'use strict';
//房间模块
var roomMgr = require("./roommgr");//用户模块
var userMgr = require("./usermgr");
//数据库模块
var db = require("../utils/db");
//胡牌算法
var hupai = require("./zunyi_hupai");
//串行异步
var async = require("async");
//配置
var configPai = require("./config_tool");
//api
var _ = require("lodash");
//常量
var constant = require('../utils/constant');
//鸡牌算分
var _Jipai = require('./jiPai');

var positionService = require("../services/positionService");

var log4js = require('log4js');
var log = log4js.getLogger("zunyiGameMgr");

var action_count = 0;
var timer = null;

/***********************全局定义******************/
//游戏数据
var games = {};
//座位数据
var gameSeatsOfUsers = {};
//解散列表
var dissolvingList = [];
//白板牌
var baibanPai = 29;
//最大牌
var maxPai = 26;

//无效牌
var INVALID_PAI = -1;
//任意牌(任意一张牌来了都胡)
var anyHuPai = INVALID_PAI;
//动作枚举
var ACTION_CHUPAI = 1;
var ACTION_MOPAI = 2;
var ACTION_PENG = 3;
var ACTION_GANG = 4;
var ACTION_HU = 5;
var ACTION_ZIMO = 6;
var ACTION_CHI = 7;

//底数
var DISHU_FIRST = [2, 4, 6, 8];
var DISHU_SECOND = [2, 2, 2, 2];

//当前房间当前局结算
var roomRes = {};

var strCoverMap = {
    0: 'jingJiScore',
    1: 'yingJiScore',
    2: 'wuJiScore',
    3: 'waJiScore'
};

// 通过摸的牌找到左右牌
function getLeftRightPaiByHuFanPai(fanPai, fanJi) {
    fanJi[0] = fanPai;
    if (fanPai % 9 >= 1 && fanPai % 9 < 8) {
        fanJi[1] = fanPai - 1;
        fanJi[2] = fanPai + 1;
    } else {
        if (fanPai % 9 === 0) {
            fanJi[1] = fanPai + 8;
            fanJi[2] = fanPai + 1;
        } else {
            fanJi[1] = fanPai - 8;
            fanJi[2] = fanPai - 1;
        }
    }
    log.debug("fan ji == ", fanJi);
};

//开始新的一局
exports.begin = function (roomId) {
    //房间数据
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }
    roomRes[roomId] = null;

    var typeInfo = constant.CreateRoomInfo.type[roomInfo.conf.type];
    //游戏数据
    var game = {
        conf: roomInfo.conf,                 //房间配置
        roomInfo: roomInfo,                  //房间信息
        gameIndex: roomInfo.numOfGames,      //局数
        dishu: roomInfo.conf.dishu,          //底数类型
        button: roomInfo.nextButton,         //庄家
        mahjongs: new Array(108),            //麻将集合
        currentIndex: 0,                     //摸牌索引
        gameSeats: new Array(typeInfo.seatlen),             //座位信息
        turn: 0,                             //轮到某某
        chuPai: -1,                          //当前出牌
        state: constant.GameStatus.IDLE,     //游戏状态
        firstHupai: -1,                      //先胡位置索引
        yipaoduoxiang: -1,                   //一炮多响位置索引
        fangpaoshumu: -1,                    //放炮数目
        actionList: [],                      //操作记录
        hupaiList: [],                       //胡牌列表
        chupaiCnt: 0,                        //出牌次数
        paidun: 0,                           //牌墩
        chuJiPai: {
            isChongFengJi: false,            //记录当前出牌的鸡
            isOper: false,
            isjiPaiType: -1,
            pai: -1,
            turnSeat: -1,
        },
        noScoreGang: []
    };

    //更新房间配置信息
    var paiNum = constant.CreateRoomInfo.paiNum[game.conf.paiNum];
    if (paiNum) {  // 更新局数和局数钻石
        game.jushu = paiNum.jushu;
        game.jushucost = paiNum.jushucost;
        roomInfo.conf.maxGames = game.jushu;
        _Jipai.initChongFengJiConfig(game); //初始化冲锋鸡配置
        _Jipai.initJiPaiConfigRecord(game); //初始化鸡牌配置记录
    }

    //更新历史局数，分数等
    updateHistoryData(roomInfo, game);

    //打印庄家
    log.debug("current banker == " + game.button);

    //根据庄确定开牌位置
    openPaiPosByBanker(roomInfo, game);

    //初始化座位
    var seats = roomInfo.seats;
    for (var i = 0; i < typeInfo.seatlen; ++i) {
        //游戏座位数据
        var data = game.gameSeats[i] = {};
        //游戏数据
        data.game = game;
        //座位索引
        data.seatIndex = i;
        //玩家标识
        data.userId = seats[i].userId;
        // 玩家配牌
        data.isConfigPai = seats[i].isConfigPai;
        //持有的牌
        data.holds = [];
        //打出的牌
        data.folds = [];
        //打出的牌被吃碰和点杠后剩余的牌
        data.lestFolds = [];
        //暗杠的牌
        data.angangs = [];
        //点杠的牌
        data.diangangs = [];
        //弯杠的牌
        data.wangangs = [];
        //碰了的牌
        data.pengs = [];
        //缺一门
        data.que = -1;
        //吃了的牌
        data.chis = [];
        //玩家手上的牌的数目，用于快速判定碰杠
        data.countMap = {};
        //玩家听牌，用于快速判定胡了的番数
        data.tingMap = {};
        //模式
        data.pattern = '';
        //胡的牌型
        data.paiType = 0;
        //是否可以杠
        data.canGang = false;
        //用于记录玩家可以杠的牌
        data.gangPai = [];
        //是否可以碰
        data.canPeng = false;
        //每个人报听只有一次机会
        data.baoTingCount = 1;
        // 自动出牌状态
        data.acChuPaiStatus = false;
        //是否可以报听
        data.canBaoTing = false;
        //是否可以胡
        data.canHu = false;
        //是否可以出牌
        data.canChuPai = false;
        //是否胡了
        data.hued = false;
        //是否是自摸
        data.iszimo = false;
        //杠胡
        data.isGangHu = false;
        //动作
        data.actions = [];
        //分数
        data.score = 0;
        //上次番杠座位
        data.lastFangGangSeat = -1;
        //是否操作 1--客户端已点击吃 2--客户端已点击碰 3--客户端已点击杠 4--客户端已点击胡 
        data.isOper = 0;
        //对应上面操作的牌
        data.OperPai = 0;
        //点击吃牌按钮了的牌集合
        data.clickChi = [];
        //杠分单独算
        data.gangScore = 0;
        //是否圆缺
        data.isYuanQue = false;
        //圆缺得分
        data.yuanQueScore = [];
        //查缺得分
        data.chaQueScore = [];
        //胡牌类型集合
        data.huType = [];
        //黄庄时是否叫牌
        data.isTinged = false;
        //黄庄时查叫分
        data.tingScore = 0;
        //黄庄时是否缺门
        data.isQueMen = false;
        //点杠得分
        data.diangangScore = {};
        //弯杠得分
        data.wangangScore = {};
        //暗杠得分
        data.angangScore = {};
        //报听分
        data.baoTingScore = [];
        //杀报分
        data.shaBaoScore = [];
        //听牌结果
        data.tingResult = [];
        //放炮或自摸得分
        data.huScore = 0;
        //弃碰弃胡
        data.qiPengHu = [];
        //期望配置的牌
        data.hopeMopai = -1;
        //所有杠过的牌
        data.gangs = [];
        //听牌集合
        data.tingResult = [];
        //是否报听
        data.isBaoTing = false;
        //是否正在操作听牌
        data.isOperBaoTing = false;
        //报听后有杠的听结果
        data.tempBaoTingTingResultInGangDown = [];
        //座位数据
        gameSeatsOfUsers[data.userId] = data;
    }

    game.gameOverCallback = [];
    var fanJiDelay = 1000;
    var jiesuangDelay = 0;
    if (game.shangXiaJi) {  //  如果配置了上下鸡。
        game.gameOverCallback.push(
            function (game, seatData, status) {
                setTimeout(
                    function () {
                        if (status === 0) {  //只有正确胡牌才允许翻鸡牌
                            // 翻牌
                            var fanPai = huPaifanPai(game);
                            log.debug('fan pai == ', fanPai);
                            //通知前端，翻牌
                            userMgr.broadcastInRoom('paidun_fanpai_push', { fanPai: fanPai }, seatData.userId, true);
                        }
                    }, fanJiDelay)
            }
        );
        jiesuangDelay = 2500;
    }
    game.gameOverCallback.push(
        function (game, seatData, status) {
            setTimeout(
                function () {
                    doGameOver(game, seatData.userId, status);
                }, jiesuangDelay
            );

        }
    );

    //存储每局游戏数据
    games[roomId] = game;

    //加入是否测试
    TestModel(game);

    //游戏状态推送
    userMgr.broadcastInRoom('game_status_push', { state: game.state }, seats[0].userId, true);

    //开牌墩
    openPaiDun(game);
    delayToBegin(game);
};

//更新历史数据，包括局数 历史分数等
function updateHistoryData(roomInfo, game) {
    if (!roomInfo.bcheckfinish) {
        roomInfo.numOfGames++;
        roomInfo.eachBankers.push(game.button);
    }
    else {
        db.get_eachround_data(roomInfo.id, function (data) {
            if (data != null) {
                for (var j = 0; j < data.length; ++j) {
                    roomInfo.eachBankers[j] = data[j].banker_index;
                    for (var k = 0; k < data[j].history.seats.length; ++k) {
                        //玩家每局结果
                        if (roomInfo.seats[k] === undefined || roomInfo.seats[k] == null) {
                            continue;
                        }
                        if (roomInfo.seats[k].roundScore === undefined) {
                            roomInfo.seats[k].roundScore = [];
                        }
                        if (data[j].end_time != null) {
                            roomInfo.seats[k].roundScore.push(data[j].history.seats[k].score);
                        }
                        //玩家累计结果
                        if (roomInfo.seats[k].score == null) {
                            roomInfo.seats[k].score = 0;
                        }
                        roomInfo.seats[k].score += data[j].history.seats[k].score;
                    }
                }
                //已经结束上局
                if (data[data.length - 1].end_time != null) {
                    roomInfo.numOfGames = data.length + 1;
                    roomInfo.eachBankers.push(game.button);
                    game.roomInfo.bcheckfinish = false;
                } else {
                    roomInfo.numOfGames = data.length;
                }
            }
        });
    }
}

function openPaiPosByBanker(roomInfo, game) {
    if (roomInfo.numOfGames == 1)  //庄家和开牌位置
    {
        //随机出庄家的位置
        var point1 = 0;
        var point2 = 0;
        if (game.button === 0) //庄家就是开房者
        {
            point1 = Math.floor(1 + (Math.random() * 6));
            point2 = (point1 < 5) ? (5 - point1) : (9 - point1);
        }
        else {
            point1 = Math.floor(1 + (Math.random() * 6));
            var times = 0;
            do {
                ++times;
                if (times > 10)  //防止随机数异常
                {
                    point1 = 5;
                    point2 = 2;
                    break;
                }
                point2 = Math.floor(1 + (Math.random() * 6));
            } while ((point1 + point2) == 5 || (point1 + point2) == 9);
        }
        if (roomInfo.seats.length > 0) {
            userMgr.broadcastInRoom('banker_push', { button: game.button, point1: point1, point2: point2 }, roomInfo.seats[0].userId, true);
        }
    }
}

function huPaifanPai(game) {
    //  最后一张牌胡牌，最后胡的那张牌就为翻的牌了
    var fanPai = game.mahjongs[game.currentIndex];
    log.debug("huPaifanPai", fanPai, game.huPaiFanJi);
    getLeftRightPaiByHuFanPai(fanPai, game.huPaiFanJi);
    return fanPai;
}

function openPaiDun(game) {
    (function () {
        setTimeout(function () {
            var point1 = Math.floor(1 + (Math.random() * 6));
            var point2 = Math.floor(1 + (Math.random() * 6));
            if (game.roomInfo.seats.length > 0) {
                game.paidun = point1 + point2;
                userMgr.broadcastInRoom('paidun_push', { point1: point1, point2: point2, banker: game.button }, game.roomInfo.seats[0].userId, true);
            }
        }, 1000);
    })(game);
}

function delayToBegin(game) {
    (function () {
        setTimeout(function () {
            //操作完了就开始游戏
            gameBeginPush(game);
            if (!game.roomInfo.bcheckfinish) {
                db.store_eachround_history(game, game.roomInfo);
            }
            else {
                game.roomInfo.bcheckfinish = false;
            }
        }, 3000);
    })(game);
}

function gameBeginPush(game) {
    //房间信息
    var roomInfo = game.roomInfo;

    //剩多少张牌
    var numOfMJ = game.mahjongs.length - game.currentIndex;

    for (var i = 0; i < game.gameSeats.length; ++i) {
        var userId = game.gameSeats[i].userId;

        //通知玩家手牌
        userMgr.sendMsg(userId, 'game_holds_push', game.gameSeats[i].holds);
        log.debug("begin holds == ", game.gameSeats[i].userId, game.gameSeats[i].holds);

        //通知还剩多少张牌
        userMgr.sendMsg(userId, 'mj_count_push', numOfMJ);

        //通知还剩多少局
        userMgr.sendMsg(userId, 'game_num_push', roomInfo.numOfGames);

        //游戏开始通知
        //userMgr.sendMsg(userId, 'game_begin_push', { banker: game.button });

        //通知准备定缺
        userMgr.sendMsg(userId, 'game_dingque_push');

        //圆缺得分
        calScoreIfYuanQue(game, game.gameSeats[i]);
    }

    //游戏状态推送
    game.state = constant.GameStatus.DINGQUE;
    userMgr.broadcastInRoom('game_status_push', { state: game.state }, game.gameSeats[0].userId, true);
}

//判断是否圆缺
function isYuanQue(seat) {
    var type = {};
    var typeNum = 0;
    for (var i = 0; i < seat.holds.length; ++i) {
        var mjType = getMJType(seat.holds[i]);
        if (type[mjType] === undefined) {
            type[mjType] = 1;
            ++typeNum;
        }
    }
    return (typeNum <= 2);
}

//如果是圆缺,每人给3分
function calScoreIfYuanQue(game, seat) {
    if (isYuanQue(seat)) {
        seat.isYuanQue = true;
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var eachSeat = game.gameSeats[i];
            if (eachSeat.seatIndex != seat.seatIndex) {
                eachSeat.score -= 3;
                eachSeat.yuanQueScore.push({
                    score: -3,
                    seatId: seat.seatIndex,
                });
                log.debug("user id == seat score == ", eachSeat.userId, eachSeat.score);
            }
        }
        seat.score += ((game.gameSeats.length - 1) * 3);
        seat.yuanQueScore.push({
            score: 3 * (game.gameSeats.length - 1),
            seatId: seat.seatIndex,
        });
        log.debug("user id == " + seat.userId + " seat score == " + seat.score);
    }
}

//获取麻将类型
function getMJType(id) {
    if (id >= 0 && id < 9) {
        //筒
        return constant.PaiType.TONG;
    }
    else if (id >= 9 && id < 18) {
        //条
        return constant.PaiType.TIAO;
    }
    else if (id >= 18 && id < 27) {
        //万
        return constant.PaiType.WAN;
    }
}

//洗牌
function shuffle(game) {
    var mahjongs = game.mahjongs;
    //筒 0 ~ 8 表示筒子,条 9 ~ 17表示条子,万 18 ~ 26表示万,风 27 ~ 29表示中、发、白,30~33表示风
    var index = 0;
    for (var i = 0; i <= maxPai; ++i) {
        for (var c = 0; c < 4; c++) {
            mahjongs[index] = i;
            index++;
        }
    }

    //洗牌
    for (i = 0; i < mahjongs.length; ++i) {
        var lastIndex = mahjongs.length - 1 - i;
        index = Math.floor(Math.random() * lastIndex);
        var t = mahjongs[index];
        mahjongs[index] = mahjongs[lastIndex];
        mahjongs[lastIndex] = t;
    }
}


function sortPai(pais) {
    pais.sort(function (a, b) {
        return a - b;
    });
    return pais;
}

function analyzeConfigPai(paiObj) {
    var paiKeys = Object.keys(paiObj);
    //  过虑得到单牌 并且 只留筒， 条， 万
    var filterDanPaiKeys = paiKeys.filter(function (pai) {
        if (paiObj[pai] === 1 && pai <= 26) {
            return pai;
        }
    });
    filterDanPaiKeys = filterDanPaiKeys.map(function (pai) { return parseInt(pai); });
    // 筒， 条， 万 之外的单牌
    var filterOtherPaiKeys = paiKeys.filter(function (pai) {
        if (paiObj[pai] === 1 && pai > 26) {
            return pai;
        }
    });
    filterOtherPaiKeys = filterOtherPaiKeys.map(function (pai) { return parseInt(pai); });

    //  对牌数量
    var duiPaiNumber = paiKeys.filter(function (pai) {
        if (paiObj[pai] >= 2) {
            return pai;
        }
    }).length;

    // 排序
    sortPai(filterDanPaiKeys);

    var removePai = []; // 记录可以被移除的牌
    var configPai = [];  // 配牌
    var lianPaiNumber = 0;

    while (filterDanPaiKeys.length > 0) {
        var pai = filterDanPaiKeys.shift();
        //  不写太复杂故只处理连子的情况  [2 3] => [补4]   如果有中断牌即不处理例如  [2 4] => 不补
        if (pai % 9 <= 6 && ~filterDanPaiKeys.indexOf(pai + 1)) {
            pai = filterDanPaiKeys.shift();
            if (!~filterDanPaiKeys.indexOf(pai + 1)) {
                // 不存在吃的牌即保存将要配的牌
                configPai.push(pai + 1);
            }
            else {
                lianPaiNumber++;
                filterDanPaiKeys.shift();
            }
        }
        else {
            removePai.push(pai);
        }
    }
    return {
        configPai: configPai,
        removePai: removePai.concat(filterOtherPaiKeys),
        lianPaiNum: lianPaiNumber,
        duiPaiNum: duiPaiNumber
    };
}

function isConfigPais(game) {
    // 判断是否配牌了
    var isConfig = false;
    for (var i = 0; i < game.gameSeats.length; i++) {
        log.debug(game.gameSeats[i].isConfigPai);
        if (game.gameSeats[i].isConfigPai === 1) {
            isConfig = true;
        }
    }
    return isConfig;
}


function configPais(game) {
    // 判断是否配牌了
    var seatIndex = game.button;
    var userLen = game.gameSeats.length;
    var paiList = {};
    for (var i = 0; i < userLen; i++) {
        paiList[i] = {};
    }
    for (i = 0; i < userLen * 13; i++) {
        var pai = game.mahjongs[i];
        if (paiList[seatIndex][pai]) {
            paiList[seatIndex][pai]++;
        } else {
            paiList[seatIndex][pai] = 1;
        }
        seatIndex++;
        seatIndex %= game.gameSeats.length;
    }
    //发牌后接下来牌堆开始的牌
    var paidui_start_index = game.mahjongs.slice(userLen * 13, game.mahjongs.length);
    for (i = 0; i < Object.keys(paiList).length; i++) {
        if (game.gameSeats[i].isConfigPai === 1) {

            //返回 要配置的可以连的牌，和可以删除的牌
            var configPaiList = analyzeConfigPai(paiList[i]);
            log.debug(configPaiList);
            if (configPaiList.duiPaiNum + configPaiList.lianPaiNum >= 4) {
                // log.debug('bupeipai-------');
                continue;
            }

            // 随机要补的张数
            var rand = 1;
            var randN = parseInt(Math.random() * 100 + 1);
            if (randN > 0 && randN <= 70) {
                rand = 1;
            } else if (randN > 70 && randN <= 90) {
                rand = 2;
            } else {
                continue;
            }
            if (configPaiList.configPai.length >= rand || configPaiList.removePai.length >= rand) {
                var baseNumber = 1;
                // 随机，是配连子，还是配对子
                for (var j = 0; j < rand; j++) {

                    if (configPaiList.configPai.length > 0) {
                        baseNumber = 2;
                    } else {
                        baseNumber = 1;
                    }
                    if (configPaiList.removePai.length < 1) {  //条件不满足不配牌
                        break;
                    }
                    var kindKey = parseInt(Math.random() * baseNumber + 0);
                    if (kindKey === 0) {
                        //配对子 只从移除列表里配
                        if (configPaiList.removePai.length >= 2) {
                            key = parseInt(Math.random() * configPaiList.removePai.length + 0);
                            pai = configPaiList.removePai[key];
                            configPaiList.removePai.splice(key, 1);
                            log.debug("用户:" + game.gameSeats[i].userId + ",坐位号:" + i, " 配对子:", pai);
                            endPos = paidui_start_index.indexOf(pai);
                            log.debug("查找牌堆所在下标：", endPos);
                            if (endPos !== -1) {
                                paidui_start_index.splice(endPos, 1); //从牌堆取出
                                log.debug("配置对子前" + pai + "数量：" + paiList[i][pai]);
                                paiList[i][pai]++;  //  不存在为0的牌计数
                                log.debug("配置对子后" + pai + "数量：" + paiList[i][pai]);
                                //随机干掉整理过的手牌
                                key = parseInt(Math.random() * configPaiList.removePai.length + 0);
                                pai = configPaiList.removePai[key];
                                configPaiList.removePai.splice(key, 1);
                                paiList[i][pai] = 0;
                                paidui_start_index.push(parseInt(pai));
                            }
                        }

                    } else {   // 配连子
                        var key = parseInt(Math.random() * configPaiList.configPai.length + 0);
                        pai = configPaiList.configPai[key];
                        configPaiList.configPai.splice(key, 1);
                        log.debug("用户:" + game.gameSeats[i].userId + ",坐位号:" + i, " 配连子:", pai);
                        var endPos = paidui_start_index.indexOf(pai);
                        log.debug("查找牌堆所在下标：", endPos);
                        //牌堆有才配
                        if (endPos !== -1) {
                            paidui_start_index.splice(endPos, 1); //从牌堆取出
                            log.debug("配置连子前" + pai + "数量：" + paiList[i][pai]);
                            if (paiList[i][pai]) {
                                paiList[i][pai]++;
                            } else {
                                paiList[i][pai] = 1;
                            }
                            log.debug("配置连子后" + pai + "数量：" + paiList[i][pai]);
                            //随机干掉整理过的手牌
                            key = parseInt(Math.random() * configPaiList.removePai.length + 0);
                            pai = configPaiList.removePai[key];
                            configPaiList.removePai.splice(key, 1);
                            paiList[i][pai] = 0;
                            paidui_start_index.push(parseInt(pai));
                        }
                    }
                }
            }
        }
    }
    // 还原所有牌
    // 恢复牌数组
    seatIndex = game.button;
    var mahjongs = [];
    var recoverPaiList = new Array(userLen);
    for (i = 0; i < userLen; i++) {
        recoverPaiList[i] = [];
        for (pai in paiList[seatIndex]) {
            for (var j = 0; j < paiList[seatIndex][pai]; j++) {
                recoverPaiList[i].push(parseInt(pai));
            }
        }
        seatIndex++;
        seatIndex %= game.gameSeats.length;
    }
    log.debug(recoverPaiList);
    for (i = 0; i < 13; i++) {
        recoverPaiList.forEach(function (recoverPai) {
            mahjongs.push(recoverPai.shift());
        });
    }
    mahjongs = mahjongs.concat(paidui_start_index);
    return mahjongs;
}


//发牌
function deal(game) {
    //强制清0
    game.currentIndex = 0;
    //每人13张 一共 13*4 ＝ 52 庄家多一张 53张
    var seatIndex = game.button;
    // 处理配牌
    if (isConfigPais(game)) {
        var configMahjongs = configPais(game);
        var cmpConfigMahjongsOne = configMahjongs.slice(0, configMahjongs.length);
        var cmpMahjongsTwo = game.mahjongs.slice(0, game.mahjongs.length);
        if (_.isEqual(sortPai(cmpConfigMahjongsOne), sortPai(cmpMahjongsTwo))) {
            // log.debug('fuzhipeipai....');
            game.mahjongs = configMahjongs;
        }
    }
    for (var i = 0; i < 13 * game.gameSeats.length; i++) {
        if (game.gameSeats[seatIndex].holds == null) {
            game.gameSeats[seatIndex].holds = [];
        }
        //摸牌
        mopai(game, seatIndex);

        //下一个
        seatIndex++;
        seatIndex %= game.gameSeats.length;
    }

    //检测是否听牌
    // for (i = 0; i < game.gameSeats.length; ++i) {
    //     checkCanTingPai(game, game.gameSeats[i]);
    // }

    //庄家多摸最后一张
    var pai = mopai(game, game.button);

    // //检查是否可以暗杠或者胡
    // checkCanAnGang(game, game.gameSeats[game.button]);

    // //检查胡，直杠，弯杠
    // checkCanWanGang(game, game.gameSeats[game.button], pai);

    // //检查看是否可以和
    // checkCanHu(game, game.gameSeats[game.button], pai, 1);
    seatIndex++;

    //设置庄家
    game.turn = game.button;
}

//摸牌
function mopai(game, seatIndex) {
    // 荒庄
    if (game.currentIndex === game.mahjongs.length) {
        return -1;
    }
    var seatData = game.gameSeats[seatIndex];
    //var pai = (-1 == seatData.hopeMopai) ? game.mahjongs[game.currentIndex] : seatData.hopeMopai;
    // if (game.mahjongs.length > game.currentIndex + 10)
    //     game.currentIndex = game.currentIndex + 10;
    // else
    //     game.currentIndex++;
    var pai = game.mahjongs[game.currentIndex];
    seatData.holds.push(pai);
    seatData.hopeMopai = -1;

    //统计牌的数目
    if (seatData.countMap[pai] == null) {
        seatData.countMap[pai] = 1;
    }
    else {
        seatData.countMap[pai] += 1;
    }
    game.currentIndex++;
    return pai;
}

//获取配牌器配置
exports.getPeipaiConfig = function (userId) {
    db.isCanMoAnyPai(userId, function (ret) {
        userMgr.sendMsg(userId, 'get_pei_pai_config_push', ret.isMoAnyPai);
    });
}

//期望的摸牌
exports.hopeMoPai = function (userId, pai) {
    var seatData = gameSeatsOfUsers[userId];
    // if (seatData != null) {
    //     db.isCanMoAnyPai(userId, function (ret) {
    //         if (ret.isMoAnyPai == 1) {
    //             seatData.hopeMopai = pai;
    //         }
    //     });
    // }

    // timer = setInterval(function () {
    //     var data = {
    //         pai: pai, hu: true, peng: true,
    //         gang: true, gangpai: [pai]
    //     };

    //     var seatData = gameSeatsOfUsers[userId];

    //     // //通知还剩多少张牌
    //     // userMgr.sendMsg(userId, 'mj_count_push', 2);

    //     // //通知还剩多少局
    //     // userMgr.sendMsg(userId, 'game_num_push', 1);

    //     // //游戏开始通知
    //     // userMgr.sendMsg(userId, 'game_begin_push', { banker: 0 });

    //     // //通知准备定缺
    //     // userMgr.sendMsg(userId, 'game_dingque_push');

    //     // //手牌
    //     // userMgr.sendMsg(userId, 'game_holds_push', seatData.holds);

    //     userMgr.sendMsg(userId, 'game_action_push', data);

    //     ++action_count;
    //     log.debug("action count = ", action_count);
    //     if (action_count == 2000) {
    //         clearInterval(timer);
    //     }
    // }, 330);
}
function checkIsBaoTingStatus(baoTingPais) {
    for (var pai in baoTingPais) {
        if (baoTingPais[pai] === true) {
            return true;
        }
    }
    return false;
}

function baoTingCall(game, turnSeat) {
    var quePaiCount = getDingQueHoldsCount(turnSeat);
    log.debug("baotingCall", quePaiCount);
    var baoTingStatus = false;
    if (quePaiCount.count === 0) {
        baoTingCheckTingPai(game, turnSeat);
    }
    if (quePaiCount.count === 1) {
        isTingPaiChuQuePai(turnSeat, quePaiCount.pai);
    }

    baoTingStatus = checkIsBaoTingStatus(turnSeat.tingPaiStatus ? turnSeat.tingPaiStatus : {});
    log.debug("checkbaoting===>", turnSeat.tingPaiStatus);
    if (baoTingStatus) {
        log.debug("baoting Send===>");
        userMgr.sendMsg(turnSeat.userId, 'baoting_holds_push', { userId: turnSeat.userId, tingPaiStatus: turnSeat.tingPaiStatus });
    }
    return baoTingStatus;
}

// function baoTingCall(game, turnSeat) {
//     var quePaiCount = getDingQueHoldsCount(turnSeat);
//     if (quePaiCount.count === 0) {
//         baoTingCheckTingPai(game, turnSeat);
//         console.log("hahahahahaha===>", turnSeat.tingPaiStatus);
//         userMgr.sendMsg(turnSeat.userId, 'baoting_holds_push', { userId: turnSeat.userId, tingPaiStatus: turnSeat.tingPaiStatus });
//         return;
//     }
//     if (quePaiCount.count === 1) {
//         isTingPaiChuQuePai(turnSeat, quePaiCount.pai);
//         userMgr.sendMsg(turnSeat.userId, 'baoting_holds_push', { userId: turnSeat.userId, tingPaiStatus: turnSeat.tingPaiStatus });
//         return;
//     }
// }

//定缺完成
function dingQueEndMoPai(game) {
    var turnSeat = game.gameSeats[game.turn];

    //更新游戏状态
    game.state = constant.GameStatus.PYAYING;
    userMgr.broadcastInRoom('game_status_push', { state: game.state }, turnSeat.userId, true);
    if (!turnSeat.canHu) {  //不能胡
        if (turnSeat.baoTingCount > 0) {
            turnSeat.canChuPai = true;
            userMgr.broadcastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);
            game.chuPai = -1;
            if (baoTingCall(game, turnSeat)) {
                return;
            }
        }
    }

    //广播通知玩家出牌方
    turnSeat.canChuPai = true;
    userMgr.broadcastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);
    game.chuPai = -1;
    //通知玩家做对应操作
    sendOperations(game, turnSeat, -1);
}

function getDingQueHoldsCount(seatData) {
    var count = 0;
    var fristQuePai = -1;
    var fristStatus = false;
    for (var pai in seatData.countMap) {
        if (!!seatData.countMap[pai]) {
            if (getMJType(pai) === seatData.que) {
                count += seatData.countMap[pai];
                if (!fristStatus) {
                    fristStatus = true;
                    fristQuePai = pai;
                }
            }
        }
    }
    return {
        count: count,
        pai: fristQuePai
    };
}


function isTingPaiChuQuePai(seatData, quePai) {
    quePai = parseInt(quePai);
    var tingPaiStatus = {};
    var tingResult = [];
    var tingPaiHolds = seatData.holds.slice();
    var pengPai = [];

    for (var pai in seatData.countMap) {
        if (!!seatData.countMap[pai]) {
            tingPaiStatus[pai] = false;
        }
    }

    var addPais = seatData.angangs.concat(seatData.diangangs).concat(seatData.wangangs).concat(seatData.pengs);
    addPais.forEach(function (pai) {
        pengPai = pengPai.concat([pai, pai, pai]);
    });

    var index = tingPaiHolds.indexOf(quePai);
    if (index !== -1) {
        tingPaiHolds.splice(index, 1);
    }
    if (tingPaiHolds.length === 14) {
        seatData.tingPaiStatus = tingPaiStatus;
        return tingPaiStatus;
    }
    var mdeck = new hupai.MDeck();
    mdeck.combineDeck(tingPaiHolds).pushOperPai(pengPai);
    if (mdeck.length() % 3 === 1) {  // 听牌必须是能被3整除余1
        tingResult = mdeck.tingPai();
    }
    tingPaiStatus[quePai] = tingResult.length > 0 ? true : false;
    seatData.tingPaiStatus = tingPaiStatus;
    return tingPaiStatus;
}

//摸牌校验次序
function isHasMoPai(game) {
    log.debug("isHasMoPai room id =  verify seat num ", game.roomInfo.id, game.gameSeats.length);
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat.holds.length > 0 && (seat.holds.length % 3 == 2)) {
            if (game.turn != seat.seatIndex) {
                log.debug("isHasMoPai before modify game turn is ", game.turn);
                game.turn = seat.seatIndex;
                log.debug("isHasMoPai after modify game turn is ", game.turn, seat.userId);
            }
            game.chuPai = -1;
            seat.canChuPai = true;
            userMgr.broadcastInRoom('game_chupai_push', seat.userId, seat.userId, true);
            log.debug("isHasMoPai check user has mo pai ", seat.userId, seat.holds[seat.holds.length - 1]);
            return true;
        }
    }
    return false;
}

//下一个摸牌
function nextUserMoPai(game) {

    //校验是否已经有人摸过牌了,程序误入了该代码
    if (isHasMoPai(game)) {
        log.warn("mo pai fail for one man has done!");
        return;
    }

    // 清除鸡记录
    if (game.chuJiPai.isjiPaiType !== -1) {
        log.debug("set heself ==> jipai", game.chuPai, game.chuJiPai);
        if (!game.chuJiPai.isOper) {
            _Jipai.insertJiPaiRecord(game, game.chuJiPai.turnSeat, game.chuJiPai.pai, game.chuJiPai.turnSeat, {
                oper: constant.operType.CHU,
                isChongFengJi: game.chuJiPai.isChongFengJi
            });
        }
        game.chuJiPai.turnSeat = -1;
        game.chuJiPai.isChongFengJi = false;
        game.chuJiPai.isjiPaiType = -1;
        game.chuJiPai.pai = -1;
        game.chuJiPai.isOper = false;
    }
    game.chuPai = -1;
    var turnSeat = game.gameSeats[game.turn];
    turnSeat.lastFangGangSeat = -1;

    //去掉弃碰弃胡记录
    delQiPengHu(turnSeat);

    var pai = mopai(game, game.turn);
    if (pai == -1) {
        // for (var index in game.gameOverCallback) {
        //     game.gameOverCallback[index](game, turnSeat, 1);
        // }
        return;
    }

    var numOfMJ = game.mahjongs.length - game.currentIndex;
    userMgr.broadcastInRoom('mj_count_push', numOfMJ, turnSeat.userId, true);
    recordGameAction(game, game.turn, ACTION_MOPAI, pai);

    //通知前端新摸的牌
    userMgr.sendMsg(turnSeat.userId, 'game_mopai_push', { holds: turnSeat.holds, baoTingStatus: turnSeat.acChuPaiStatus });
    userMgr.broadcastInRoom('mopai_push', pai, turnSeat.userId, false);
    log.debug("userId == " + turnSeat.userId + " mo pai == " + pai);

    // 检测记录鸡牌
    if (game.jiPaiList.length > 0) {
        var jiPaiResult = _Jipai.checkAndSetChongFengJiPai(game, turnSeat.seatIndex, pai);

        if (jiPaiResult.isjiPaiType !== -1) {
            log.debug("mopai record ==> jipai", pai);
            game.chuJiPai = jiPaiResult;  //记录打出的是鸡牌
        }
    }

    //检查看是否可以和
    checkCanHu(game, turnSeat, pai, 1);

    //广播通知玩家出牌方
    turnSeat.canChuPai = true;
    userMgr.broadcastInRoom('game_chupai_push', turnSeat.userId, turnSeat.userId, true);

    //通知玩家做对应操作
    if (!turnSeat.canHu) {  //如果不能胡牌才检测报听
        if (turnSeat.baoTingCount > 0) {
            if (baoTingCall(game, turnSeat)) {
                return;
            }
        }
    }

    //检查是否可以暗杠或者胡
    checkCanAnGang(game, turnSeat);

    //检查胡，直杠，弯杠
    checkCanWanGang(game, turnSeat, pai);

    //发送动作
    sendOperations(game, turnSeat, game.chuPai);
}

//去掉弃碰弃胡记录
function delQiPengHu(seatData) {
    var evens = _.remove(seatData.qiPengHu, function (data) {
        return data.operPlayer == seatData.userId;
    });
    log.debug("del qi pai return and lest ", evens, seatData.qiPengHu);
}

// 定缺
exports.dingQue = function (userId, type) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.warn("can't find user game data.");
        return;
    }

    var game = seatData.game;
    if (game.state !== constant.GameStatus.DINGQUE) {
        log.warn("can't recv dingQue when game.state == " + game.state);
        return;
    }

    if (type == INVALID_PAI) {
        log.warn("ding que value is wrong! ", type);
        return;
    }

    if (seatData.que == INVALID_PAI) {
        seatData.que = parseInt(type);
    } else {
        log.warn("server has ding que == ", seatData.que);
        return;
    }

    var allDingQue = true;
    for (let i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat.que == INVALID_PAI) {
            allDingQue = false;
        }
    }

    log.debug("user id == " + userId + " ding que type == " + seatData.que);

    //检查玩家可以做的动作
    //如果4个人都定缺了，通知庄家出牌
    if (allDingQue) {

        //游戏开始通知
        userMgr.broadcastInRoom('game_begin_push', { banker: game.button }, game.gameSeats[game.button].userId, true);

        construct_game_base_info(game);
        var arr = new Array(game.gameSeats.length);
        arr.fill(1);
        for (var i = 0; i < game.gameSeats.length; ++i) {
            arr[i] = game.gameSeats[i].que;
            checkCanTingPai(game, game.gameSeats[i]);
        }
        var bankerData = game.gameSeats[game.button];
        checkCanHu(game, bankerData, -1, 1);
        checkCanAnGang(game, bankerData);
        userMgr.sendMsg(seatData.userId, 'game_dingque_notify_push', type);
        userMgr.broadcastInRoom('game_dingque_finish_push', arr, seatData.userId, true);

        //定缺结束后摸牌
        dingQueEndMoPai(game);
    } else {
        userMgr.sendMsg(seatData.userId, 'game_dingque_notify_push', type);
    }
};

exports.userDistance = function (_msgUserId, userIds) {
    log.debug("user ids == ", userIds);
    if (userIds.toString() === "[]") {
        userMgr.sendMsg(userId, 'user_distance_push', {});
    }
    if (_.isString(userIds)) {
        userIds = userIds.slice(1, userIds.length - 1);
        userIds = userIds.split(',');
    }
    // log.debug(userIds);
    if (userIds.length === 1) {
        userMgr.sendMsg(_msgUserId, 'user_distance_push', []);
        return;
    }
    var userId = userIds.shift();
    var otherUserId = userIds;
    // 计算矩离
    log.debug(userId, otherUserId);
    positionService.getCurrUserRelativeOtherUsersDistance(userId, otherUserId, function (result) {
        userMgr.sendMsg(_msgUserId, 'user_distance_push', result);
    });
};

function checkIsExistQuePai(seatData) {
    //检查手上的牌是不是已打缺，如果未打缺，则不进行判定


    for (var i = 0; i < seatData.holds.length; ++i) {
        var pai = seatData.holds[i];
        if (getMJType(pai) === seatData.que) {
            return true;
        }
    }

    return false;
}

//报听
exports.baoTing = function (userId, isBaoTing) {
    //座位数据
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.warn("can't find user game data.");
        return;
    }
    if (_.isString(isBaoTing)) {
        isBaoTing = (isBaoTing == "true") ? true : false;
    }

    log.debug("recv baoting ===>", isBaoTing);
    var game = seatData.game;
    if (isBaoTing) {
        seatData.isBaoTing = isBaoTing;
        seatData.isOperBaoTing = isBaoTing;
        var arr = new Array(game.gameSeats.length);
        arr.fill(false);
        for (var i = 0; i < game.gameSeats.length; ++i) {
            arr[i] = !!game.gameSeats[i].isBaoTing;
        }
        userMgr.sendMsg(seatData.userId, 'baoting_push', isBaoTing);
        userMgr.broadcastInRoom('baoting_icon_push', arr, seatData.userId, true);
        if (seatData.canGang) {
            seatData.canGang = false;
        }
        return;
    } else {
        userMgr.sendMsg(seatData.userId, 'baoting_push', isBaoTing);
        if (!!!seatData.selectBaotingGangStatus) {
            sendGangOperations(game, seatData);
            seatData.selectBaotingGangStatus = true;
        }
    }
};


//出牌校验次序
function isErrorChuPai(game, seatData) {
    log.debug("isErrorChuPai room id =  verify seat num ", game.roomInfo.id, game.gameSeats.length);
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat.holds.length > 0 && (seat.holds.length % 3 == 2)) {
            if (seat.seatIndex != seatData.seatIndex) {
                log.debug("isErrorChuPai before modify game turn is ", game.turn);
                game.turn = seat.seatIndex;
                log.debug("isErrorChuPai after modify game turn is ", game.turn);
                game.chuPai = -1;
                seat.canChuPai = true;
                userMgr.broadcastInRoom('game_chupai_push', seat.userId, seat.userId, true);
                log.debug("isErrorChuPai check user has chu pai ", seat.userId, seat.holds[seat.holds.length - 1]);
                return true;
            }
        }
    }
    return false;
}

//出牌
exports.chuPai = function (userId, pai) {
    //var time1 = Date.now();

    //其中一家出的牌
    log.debug("user id == " + userId + " begin: follow is chupai data: " + pai);
    pai = Number.parseInt(pai);
    if (pai < 0) {
        log.debug("pai data is wrong.");
        return;
    }

    //座位数据
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.warn("can't find user game data.");
        return;
    }

    var game = seatData.game;

    //如果能碰杠胡的话,不能出牌
    if (!seatData.isOperBaoTing && hasOperations(seatData)) {
        log.debug("you have operation should do, so return ", seatData.userId, pai);
        log.debug("operation action has peng gang hu ", seatData.canPeng, seatData.canGang, seatData.canHu);
        return;
    }
    if (seatData.isOperBaoTing) {
        seatData.isOperBaoTing = false;
    }

    //如果不该他出，则忽略
    if (game.turn != seatData.seatIndex) {
        log.warn("not your turn.");
        return;
    }

    //已经出过牌了
    if (seatData.holds.length % 3 == 1) {
        log.debug("chu pai data error ", seatData.userId, seatData.holds);
        return;
    }

    //是否不该你出牌
    if (isErrorChuPai(game, seatData)) {
        log.debug("game turn error not chu pai user id ", seatData.userId);
        return;
    }

    //已胡
    if (seatData.hued) {
        log.debug('you have already hued. no kidding plz.');
        return;
    }
    //当前不能出牌
    if (seatData.canChuPai === false) {
        log.debug('no need chupai.');
        return;
    }

    // 有que牌，无法打出，其它非que牌。
    var isQue = checkIsExistQuePai(seatData);
    if (isQue) {
        if (getMJType(pai) !== seatData.que) {
            log.debug(pai + 'equ que pai');
            return;
        }
    }

    if (seatData.baoTingCount > 0) {  //报听还有机会
        if (!!seatData.isBaoTing) {
            log.debug("baoting is recv true......", seatData.tingPaiStatus[pai]);
            if (seatData.tingPaiStatus[pai]) {
                seatData.acChuPaiStatus = true;
            }
        }
        seatData.baoTingCount = 0; // 报听只听首轮
        seatData.tingPaiStatus = {};
    }

    //从此人牌中扣除
    var index = seatData.holds.indexOf(pai);
    if (index == -1) {
        log.warn("holds:" + seatData.holds);
        log.warn("chuPai: can't find mj." + pai);
        return;
    }
    seatData.holds.splice(index, 1);
    seatData.countMap[pai]--;

    //记录出牌
    seatData.canChuPai = false;
    game.chupaiCnt++;
    game.chuPai = pai;
    recordGameAction(game, seatData.seatIndex, ACTION_CHUPAI, pai);

    //广播出牌通知
    userMgr.broadcastInRoom('game_chupai_notify_push', { userId: seatData.userId, pai: pai, holds: seatData.holds }, seatData.userId, true);

    // userMgr.broadcastInRoom('chongfengji_push', { userId: seatData.userId, type: game.chupaiCnt % 4 }, seatData.userId, true);

    // 检测记录鸡牌
    if (game.jiPaiList.length > 0) {
        if (game.chuJiPai.isjiPaiType !== -1) { //清除没有删掉的记录
            game.chuJiPai.turnSeat = -1;
            game.chuJiPai.isChongFengJi = false;
            game.chuJiPai.isjiPaiType = -1;
            game.chuJiPai.pai = -1;
            game.chuJiPai.isOper = false;
        }
        var jiPaiResult = _Jipai.checkAndSetChongFengJiPai(game, seatData.seatIndex, pai, true);
        if (jiPaiResult.isjiPaiType !== -1) {
            game.chuJiPai = jiPaiResult;  //记录打出的是鸡牌
        }
        log.debug(jiPaiResult);
        if (jiPaiResult.isChongFengJi === true) {  // 打出的是冲锋鸡则记录
            userMgr.broadcastInRoom('chongfengji_push', { userId: seatData.userId, type: parseInt(jiPaiResult.isjiPaiType) }, seatData.userId, true);
            userMgr.broadcastInRoom('zerenji_push', _Jipai.getZenRenJiBySeatIndex(game, seatData.seatIndex), seatData.userId, true);
        }
        // var type = constant.getJiPaiTypeByPai(pai);
        // if (type !== -1) {
        //     _Jipai.insertChuJiPaiRecord(game, seatData.seatIndex, pai, type);
        // }
    }

    //检查是否有人要胡，要碰 要杠
    var hasActions = false;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        //玩家自己不检查
        if (game.turn == i) {
            continue;
        }

        //已经和牌的不再检查
        var seatInfo = game.gameSeats[i];
        if (seatInfo.hued) {
            log.debug("userId == " + seatInfo.userId + " has hued!");
            continue;
        }

        //是否能胡
        checkCanHu(game, seatInfo, pai, 0);

        if (!seatInfo.acChuPaiStatus) {  // 报听不需要检测碰，和点杠
            //是否能碰
            checkCanPeng(game, seatInfo, pai);
        }
        //是否点杠
        checkCanDianGang(game, seatInfo, pai);

        //是否能吃、碰、杠、胡
        if (hasOperations(seatInfo)) {
            //发送操作推送
            var bAction = sendOperations(game, seatInfo, pai);
            hasActions = hasActions || bAction;
        }
    }

    //是否听牌
    seatData.folds.push(game.chuPai);
    seatData.lestFolds.push(game.chuPai);
    game.chuPaiData = {
        seatIndex: seatData.seatIndex,
        chupai: game.chuPai
    };

    //是否听牌
    checkCanTingPai(game, seatData);

    //如果没有人有操作，则向下一家发牌，并通知他出牌
    if (!hasActions) {
        //没牌结束,黄庄
        if (game.currentIndex == game.mahjongs.length) {
            for (var index in game.gameOverCallback) {
                game.gameOverCallback[index](game, seatData, 1);
            }
            return;
        }

        //通知下家摸牌(发牌)
        userMgr.broadcastInRoom('guo_notify_push', { userId: seatData.userId, folds: seatData.lestFolds }, seatData.userId, true);
        game.chuPai = -1;
        moveToNextUser(game);
        nextUserMoPai(game);
    }
    else {
        //通知大家更新动画
        userMgr.broadcastInRoom('chu_pai_animation_push', { userId: seatData.userId, folds: seatData.lestFolds }, seatData.userId, true);
    }

    //log.debug("chu pai cost time == " + (Date.now() - time1) / 1000);
};



//检查是否可以碰
function checkCanPeng(game, seatData, targetPai) {
    if (findQiPaiRecord(seatData, 1, targetPai)) {
        log.debug("you have qi peng, so return. user id == , pai == ", seatData.userId, targetPai);
        return;
    }

    //未定缺
    if (seatData.que == INVALID_PAI) {
        log.debug("seat user peng que men invalid return ", seatData.userId);
        return false;
    }

    //牌为缺门牌返回
    if (getMJType(targetPai) == seatData.que) {
        log.debug('checkCanPeng ==>', targetPai, getMJType(targetPai), seatData.que);
        return;
    }

    //打印
    log.debug("userId: " + seatData.userId + " check peng have holds: " + seatData.holds);

    //除了财神外,根据个数判断
    var count = seatData.countMap[targetPai];
    if (count != null && count >= 2) {
        seatData.canPeng = true;
    }
}

//检查是否可以点杠
function checkCanDianGang(game, seatData, targetPai) {
    //未定缺
    if (seatData.que == INVALID_PAI) {
        log.debug("seat dian gang user que men invalid return ", seatData.userId);
        return false;
    }

    //牌为缺门牌返回
    if (getMJType(targetPai) == seatData.que) {
        log.debug('checkCanDianGang ==> ', targetPai, getMJType(targetPai), seatData.que);
        return false;
    }

    //校验是否牌错误
    var count = seatData.countMap[targetPai];
    if (count == 4) {
        return false;
    }

    if (count != null && count == 3) {
        seatData.canGang = true;
        seatData.gangPai.push(targetPai);
        return true;
    }
    return false;
}

//检查是否可以暗杠
function checkCanAnGang(game, seatData) {
    //未定缺
    if (seatData.que == INVALID_PAI) {
        log.debug("seat user an gang que men invalid return ", seatData.userId);
        return false;
    }

    //没摸牌
    if (seatData.holds.length % 3 == 1) {
        log.debug("seat user holds incorrect ", seatData.userId, seatData.holds);
        return false;
    }

    //循环找出暗杠牌
    var bAnGang = false;
    for (var key in seatData.countMap) {
        var pai = parseInt(key);
        if (getMJType(pai) != seatData.que) {
            //log.debug("check can gang holds pai jihe == ", seatData.countMap);
            var paiNum = seatData.countMap[pai];
            if (paiNum === 4) {
                seatData.canGang = true;
                seatData.gangPai.push(pai);
                log.debug("check can an gang == gang pai == ", pai, seatData.gangPai);
                bAnGang = true;
                continue;
            }
        }
    }
    return bAnGang;
}

//检查是否可以弯杠(自己摸起来的时候)
function checkCanWanGang(game, seatData, pai) {
    //未定缺
    if (seatData.que == INVALID_PAI) {
        log.debug("seat user wan gang que men invalid return ", seatData.userId);
        return false;
    }

    //没摸牌
    if (seatData.holds.length % 3 == 1) {
        log.debug("seat user holds incorrect ", seatData.userId, seatData.holds);
        return false;
    }

    //从碰过的牌中选
    var bWanGang = false;
    for (var i = 0; i < seatData.pengs.length; ++i) {
        var paiTmp = seatData.pengs[i];
        if (seatData.countMap[paiTmp] == 1) {
            seatData.canGang = true;
            seatData.gangPai.push(paiTmp);
            if (pai != paiTmp) {
                if (-1 == game.noScoreGang.indexOf(paiTmp)) {
                    game.noScoreGang.push(paiTmp);
                }
            }
            bWanGang = true;
        }
    }
    return bWanGang;
}

function findQiPaiRecord(seatData, type, targetPai) {
    for (var i = 0; i < seatData.qiPengHu.length; ++i) {
        var data = seatData.qiPengHu[i];
        if (data != null) {
            if (data.operPlayer == seatData.userId && targetPai == data.pai && type == data.operType) {
                return true;
            }
        }
    }
    return false;
}

//检查是否能胡 huType 0--别家放炮 1--自己摸牌胡 自摸
function checkCanHu(game, seatData, targetPai, huType) {
    game.lastHuPaiSeat = -1;

    if (findQiPaiRecord(seatData, 2, targetPai)) {
        log.debug("you have qi hu, so return, user id == , pai == ", seatData.userId, targetPai);
        return;
    }

    //缺色值校验
    if (seatData.que == INVALID_PAI) {
        log.debug("checkcanhu return for que pai = -1 ", seatData.userId);
        return;
    }

    //自摸根据最后一张判断胡牌
    if (huType == 1 && targetPai == -1) {
        targetPai = seatData.holds[seatData.holds.length - 1];
    }

    //当前牌为不缺色返回
    if (huType == 0 && getMJType(targetPai) == seatData.que) {
        log.debug("user id == " + seatData.userId + " 1 return for you have que pai == " + seatData.que);
        return;
    }

    //所有牌判断是否存在不缺色,是则返回
    if (checkIsExistQuePai(seatData)) {
        log.debug("user id == " + seatData.userId + " holds have que pai!");
        return;
    }

    //打印手牌
    log.debug("user id == " + seatData.userId + " check hu pai holds == " + seatData.holds + " hu type == " + huType);

    //放炮加入打出牌
    if (huType != 1) {
        seatData.holds.push(targetPai);
        seatData.countMap[targetPai] = (seatData.countMap[targetPai] == null) ? 1 : (seatData.countMap[targetPai] + 1);
        //log.debug("user id == " + seatData.userId + " check hu pai holds == " + seatData.holds);
    }

    //判断是否能胡
    var mdeck = new hupai.MDeck();
    var pengPai = [];
    seatData.pengs.forEach(function (pai) {
        pengPai = pengPai.concat([pai, pai, pai]);
    });
    mdeck.combineDeck(seatData.holds).pushOperPai(pengPai);
    var isHuPai = mdeck.isHu();

    //如果能胡,检测听牌列表
    if (isHuPai) {
        log.debug("user id ,result 1 == targetPai == ", seatData.userId, seatData.tingResult, targetPai);

        if (!checkTingResultPai(seatData, targetPai)) {

            log.debug("user id ,result 2 == ", seatData.userId, seatData.tingResult);
            //如果漏了这张听牌,再检测一次听牌
            //log.debug("check ting again user id == "+seatData.userId+" holds == "+seatData.holds);
            // var index = seatData.holds.indexOf(targetPai);
            // if (index != -1) {

            //     seatData.holds.splice(index, 1);
            //     --seatData.countMap[pai];

            //     checkCanTingPai(game, seatData);

            //     if (huType == 1) {
            //         seatData.holds.push(targetPai);
            //         ++seatData.countMap[pai];
            //     }

            //     if (checkTingResultPai(seatData, targetPai)) {
            //         log.debug("not hu, check ting result again, user id ,result == ", seatData.userId, seatData.tingResult);
            //     }
            //     return;
            // }
        }
    }

    if (huType != 1) {
        var index = seatData.holds.indexOf(targetPai);
        if (index === -1) {
            return log.debug("find holds not exist targetPai==", targetPai);
        }
        seatData.holds.splice(index, 1);
        seatData.countMap[targetPai] -= 1;
        log.debug("checkcanhu restore holds pai , holds ==  ", targetPai, seatData.holds);
    }
}

//黄庄时检测听牌结果集
function checkTingResultInHuangzhuang(seatData) {
    var bHasPingHu = false;
    var bHasDuiZi = false;
    var bSpecial = false;  //是否边卡吊或大宽张

    //找出胡牌类型
    for (var i = 0; i < seatData.tingResult.length; i++) {
        var tingTypes = seatData.tingResult[i];
        if (tingTypes == null) {
            continue;
        }
        var tingPais = tingTypes.tingPais;
        if (tingPais == null) {
            continue;
        }

        if (tingTypes.type == constant.TingPaiType.PINGHU) {
            bHasPingHu = true;
        }
        else if (tingTypes.type == constant.TingPaiType.DADUIZI || tingTypes.type == constant.TingPaiType.LONGQIDUI || tingTypes.type == constant.TingPaiType.QIXIAODUI) {
            bHasDuiZi = true;
        }
        else {
            bSpecial = true;
        }
    }

    //存在平胡与大对子，龙七对，小七对时，不算平胡
    if (bHasPingHu && bHasDuiZi && !bSpecial) {
        for (let i = 0; i < seatData.tingResult.length; i++) {
            let tingTypes = seatData.tingResult[i];
            if (tingTypes == null) {
                continue;
            }
            let tingPais = tingTypes.tingPais;
            if (tingPais == null) {
                continue;
            }
            if (tingTypes.type == constant.TingPaiType.PINGHU) {
                seatData.tingResult.splice(i, 1);
            }
        }
    }

    //存在平胡与边卡吊，大宽张，只算为平胡分
    if (bHasPingHu && !bHasDuiZi && bSpecial) {
        for (let i = 0; i < seatData.tingResult.length; i++) {
            let tingTypes = seatData.tingResult[i];
            if (tingTypes == null) {
                continue;
            }
            let tingPais = tingTypes.tingPais;
            if (tingPais == null) {
                continue;
            }
            if (tingTypes.type != constant.TingPaiType.PINGHU) {
                seatData.tingResult.splice(i, 1);
            }
        }
    }

    //存在任意胡牌类型时可胡牌
    if (bHasPingHu || bHasDuiZi || bSpecial) {
        return true;
    }
    return false;
}

function checkTingResultPai(seatData, targetPai) {
    var bHasPingHu = false;
    var bHasOther = false;
    var bNormal = false;
    var bQiXiaoDui = false;
    var bDaKuangZhang = false;

    //找出胡牌类型
    for (var i = 0; i < seatData.tingResult.length; i++) {
        var tingTypes = seatData.tingResult[i];
        if (tingTypes == null) {
            continue;
        }
        var tingPais = tingTypes.tingPais;
        if (tingPais == null) {
            continue;
        }
        var index = tingPais.indexOf(targetPai);
        if (index !== -1) {
            if (tingTypes.type == constant.TingPaiType.PINGHU) {
                bHasPingHu = true;
            }
            else if (tingTypes.type > constant.TingPaiType.PINGHU && tingTypes.type < constant.TingPaiType.QINGYISE) {
                bHasOther = true;
                if (tingTypes.type == constant.TingPaiType.QIXIAODUI) {
                    bQiXiaoDui = true;
                }
                else if (tingTypes.type == constant.TingPaiType.DAKUANGZI) {
                    bDaKuangZhang = true;
                }
            }
            else {
                bNormal = true;
            }
        }
    }

    //同时胡大宽张和七小对，去掉大宽张，胡七小对
    if (bQiXiaoDui && bDaKuangZhang) {
        for (let i = 0; i < seatData.tingResult.length; i++) {
            let tingTypes = seatData.tingResult[i];
            if (tingTypes == null) {
                continue;
            }
            let tingPais = tingTypes.tingPais;
            if (tingPais == null) {
                continue;
            }
            let index = tingPais.indexOf(targetPai);
            if (index !== -1 && tingTypes.type == constant.TingPaiType.DAKUANGZI) {
                tingPais.splice(index, 1);
            }
        }
    }
    log.debug("1 check can hu ting result == ", seatData.tingResult);

    //如果同时平胡和大于平胡小于清一色的特殊平胡牌, 去掉平胡
    if (bHasPingHu && bHasOther) {
        for (let i = 0; i < seatData.tingResult.length; i++) {
            let tingTypes = seatData.tingResult[i];
            if (tingTypes == null) {
                continue;
            }
            let tingPais = tingTypes.tingPais;
            if (tingPais == null) {
                continue;
            }
            let index = tingPais.indexOf(targetPai);
            if (index !== -1 && tingTypes.type == constant.TingPaiType.PINGHU) {
                seatData.tingResult.splice(i, 1);
            }
        }
    }
    log.debug("2 check can hu ting result == ", seatData.tingResult);
    //存在任意胡牌类型时可胡牌
    if (bHasPingHu || bHasOther || bNormal) {
        seatData.canHu = true;
        return true;
    }
    return false;
}

//清除状态 isKeep null--不保留最后放杠记录 1--保留最后放杠记录
function clearAllOptions(game, seatData, iskeep) {
    var fnClear = function (sd) {
        sd.canPeng = false;
        sd.canGang = false;
        sd.gangPai = [];
        sd.canHu = false;
        if (null == iskeep) {
            sd.lastFangGangSeat = -1;
        }
    }
    if (seatData) {
        fnClear(seatData);
    }
    else {
        game.qiangGangContext = null;
        for (var i = 0; i < game.gameSeats.length; ++i) {
            fnClear(game.gameSeats[i]);
        }
    }
}

function baoTingCheckTingPai(game, seatData) {
    var recordList = [];
    var tingPaiStatus = {};
    var tingResult = [];
    var mdeck = new hupai.MDeck();
    var tingPaiHolds = seatData.holds.slice();
    for (var pai in seatData.countMap) {
        pai = parseInt(pai);
        if (recordList.length > 0) {
            tingPaiHolds.push(recordList.shift());
        }
        if (seatData.countMap[pai] > 0) {
            recordList.push(pai);
            var index = tingPaiHolds.indexOf(pai);
            if (index !== -1) {
                tingPaiHolds.splice(index, 1);
            } else {
                log.debug('[error] no find tingPaiHolds isExist pai=>', pai);
            }
            mdeck.combineDeck(tingPaiHolds);
            if (mdeck.length() % 3 === 1) {  // 听牌必须是能被3整除余1
                tingResult = mdeck.tingPai();
            }
            tingPaiStatus[pai] = tingResult.length > 0 ? true : false;
            mdeck.cleanTile();
        }
    }
    seatData.tingPaiStatus = tingPaiStatus;
    return tingPaiStatus;
}

//检查听牌
function checkCanTingPai(game, seatData) {
    seatData.tingResult = [];
    //检查手上的牌是不是已打缺，如果未打缺，则不进行判定
    for (var i = 0; i < seatData.holds.length; ++i) {
        var pai = seatData.holds[i];
        if (getMJType(pai) === seatData.que) {
            log.debug("checkCanTingPai return for pai and que men == ", pai, seatData.que);
            return;
        }
    }

    if (seatData.que == INVALID_PAI) {
        log.debug("checkCanTingPai return for que pai == -1");
        return;
    }

    var holdsTmp = [];
    if (seatData.seatIndex == game.button && seatData.holds.length == 14 && seatData.folds.length == 0) {
        holdsTmp = seatData.holds.slice(0, seatData.holds.length - 1);
        log.debug("holds temp == ", holdsTmp);
    }
    log.debug("checkCanTingPai user id == ting pai holds == que men ==", seatData.userId, seatData.holds, seatData.que);

    var mdeck = new hupai.MDeck();
    var pengPai = [];
    seatData.pengs.forEach(function (pai) {
        pengPai = pengPai.concat([pai, pai, pai]);
    });
    for (let i = 0; i < seatData.gangs.length; ++i) {
        let gangTmp = seatData.gangs[i];
        if (gangTmp != null) {
            pengPai = pengPai.concat([gangTmp.pai, gangTmp.pai, gangTmp.pai]);
        }
    }

    log.debug("pengPai set == ", pengPai);
    if (holdsTmp.length > 0) {
        mdeck.combineDeck(holdsTmp).pushOperPai(pengPai);
    } else {
        mdeck.combineDeck(seatData.holds).pushOperPai(pengPai);
    }

    var tingResult = [];
    if (mdeck.length() % 3 === 1) {  // 听牌必须是能被3整除余1
        tingResult = mdeck.tingPai();
        log.debug("user id == holds == ting res setting == " + seatData.userId, seatData.holds, tingResult);
    }
    if (tingResult != null && tingResult.length > 0) {
        seatData.tingResult = tingResult;
    }

    if (isTinged(seatData)) {
        if (game.chupaiCnt === 0 && game.button == seatData.seatIndex && game.chuPai == -1) {
            seatData.pattern = "tianhu";
        }
        else if (game.chupaiCnt === 0 && game.button != seatData.seatIndex) //如果庄家放炮,闲家没抓过牌,是听牌，听地胡
        {
            seatData.pattern = "dihu";
        }
    }
}

function getSeatIndex(userId) {
    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }
    return seatIndex;
}

function getGameByUserID(userId) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return null;
    }
    var game = games[roomId];
    return game;
}
//是否能操作
function hasOperations(seatData) {
    if (seatData.canGang || seatData.canPeng || seatData.canHu) {
        return true;
    }
    return false;
}

function sendGangOperations(game, seatData) {
    var gangPai = [];
    seatData.canGang = false;
    seatData.gangPai = [];
    checkCanAnGang(game, seatData);
    var pai = seatData.holds[seatData.holds.length - 1];
    if (seatData.gangPai.length > 0) {
        gangPai = seatData.gangPai;
    }
    log.debug("sendGangOperations check-==> gane==>", seatData.canGang);
    var data = {
        pai: pai, hu: false, peng: false,
        gang: seatData.canGang, gangpai: gangPai
    };
    userMgr.sendMsg(seatData.userId, 'game_action_push', data);
}

//发送操作
function sendOperations(game, seatData, pai) {
    if (hasOperations(seatData)) {
        var gangPai = [];

        //重新检测
        if (seatData.isOper > 0) {
            userMgr.sendMsg(seatData.userId, 'game_action_push');
            return false;
        }

        var huType = (pai == -1) ? 1 : 0;
        if (seatData.canGang && pai == -1) {
            seatData.canGang = false;
            seatData.gangPai = [];
            checkCanAnGang(game, seatData);
            pai = seatData.holds[seatData.holds.length - 1];
            if (!seatData.acChuPaiStatus) {  // 报听不需要检测弯杠
                checkCanWanGang(game, seatData, pai);
            }
        }
        else if (seatData.canGang && pai != -1) {
            seatData.canGang = false;
            seatData.gangPai = [];
            checkCanDianGang(game, seatData, pai);
        }

        if (seatData.gangPai.length > 0) {
            gangPai = seatData.gangPai;
        }

        if (pai == -1) {
            pai = seatData.holds[seatData.holds.length - 1];
        }
        //重新校验
        if (seatData.canHu) {
            seatData.canHu = false;
            checkCanHu(game, seatData, pai, huType);  //重新判断是否能胡
        }

        if (seatData.canPeng) {
            seatData.canPeng = false;
            if (!seatData.acChuPaiStatus) {  // 报听不需要检测碰
                checkCanPeng(game, seatData, pai);
            }
        }

        if (seatData.acChuPaiStatus) {  //已经报听了才处理
            if (!checkCompareAfterGangBaoTingResult(game, seatData, pai, seatData.gangPai[seatData.gangPai.length - 1])) {  // 杠牌后比较不一致
                seatData.canGang = false;
            }
        }

        var data = {
            pai: pai, hu: seatData.canHu, peng: seatData.canPeng,
            gang: seatData.canGang, gangpai: gangPai
        };
        userMgr.sendMsg(seatData.userId, 'game_action_push', data);
        log.debug("发送的动作数据 ", data);
        if (seatData.canPeng) {
            log.debug("碰牌玩家号: " + seatData.userId + " 碰的牌: " + pai);
        }
        if (seatData.canGang) {
            log.debug("杠牌玩家号: " + seatData.userId + " 杠的牌: " + gangPai);
        }
        if (seatData.canHu) {
            log.debug("胡牌玩家号: " + seatData.userId + " 胡的牌: " + pai);
        }
        return hasOperations(seatData);
    }
    else {
        userMgr.sendMsg(seatData.userId, 'game_action_push');
        return false;
    }
}

function moveToNextUser(game, nextSeat) {
    game.fangpaoshumu = 0;
    //找到下一个没有和牌的玩家
    if (nextSeat == null) {
        while (true) {
            game.turn++;
            game.turn %= game.gameSeats.length;
            var turnSeat = game.gameSeats[game.turn];
            if (turnSeat.hued === false) {
                return;
            }
        }
    }
    else {
        game.turn = nextSeat;
    }
}

function isSameType(type, arr) {
    for (var i = 0; i < arr.length; ++i) {
        var t = getMJType(arr[i]);
        if (type != -1 && type != t) {
            return false;
        }
        type = t;
    }
    return true;
}

function isTinged(seatData) {
    return seatData.tingResult.length > 0 ? seatData.tingResult.length : 0;
}

function findUnTingedPlayers(game) {
    var arr = [];
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var ts = game.gameSeats[i];
        //如果没有胡，且没有听牌
        if (!ts.hued && !isTinged(ts)) {
            arr.push(i);
            recordUserAction(game, ts, "beichadajiao", -1);
        }
    }
    return arr;
}

//是否清一色
function isQingYiSe(seatData) {
    var type = getMJType(seatData.holds[0]);

    //检查手上的牌
    if (isSameType(type, seatData.holds) == false) {
        return false;
    }

    //检查杠下的牌
    if (isSameType(type, seatData.angangs) == false) {
        return false;
    }
    if (isSameType(type, seatData.wangangs) == false) {
        return false;
    }
    if (isSameType(type, seatData.diangangs) == false) {
        return false;
    }

    //检查碰牌
    if (isSameType(type, seatData.pengs) == false) {
        return false;
    }
    return true;
}

//计算清一色 type 0--放炮 1--自摸  special_type 1--杠上炮 2--抢杠胡
function calQingYiSe(game, seatData, type, special_type) {
    var index = seatData.huType.indexOf(constant.TingPaiType.QINGYISE);
    if (index == -1 && isQingYiSe(seatData)) {
        if (type == 1 || seatData.pattern == "dihu") {
            for (var j = 0; j < game.gameSeats.length; ++j) {
                let seat = game.gameSeats[j];
                if (seat.seatIndex != seatData.seatIndex) {
                    seat.score -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                    seat.huScore -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                    log.debug("user id == qingyise seat score == " + seat.userId, seat.score, seat.huScore);
                }
            }
            seatData.score += ((game.gameSeats.length - 1) * constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo);
            seatData.huScore += ((game.gameSeats.length - 1) * constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo);
        }
        else {
            for (var j = 0; j < game.gameSeats.length; ++j) {
                var seat = game.gameSeats[j];
                if (seat.seatIndex != seatData.seatIndex) {
                    //找出放炮者
                    for (var k = seat.actions.length - 1; k >= 0; --k) {
                        var action = seat.actions[k];
                        if (action.type == "fangpao")  //只有一个放炮的
                        {
                            if (special_type == 1) {
                                seat.score -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                                seat.huScore -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                                seatData.score += constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                                seatData.huScore += constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo;
                            }
                            else {
                                seat.score -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].fangpao;
                                seat.huScore -= constant.HuiPaiScore[constant.TingPaiType.QINGYISE].fangpao;
                                seatData.score += constant.HuiPaiScore[constant.TingPaiType.QINGYISE].fangpao;
                                seatData.huScore += constant.HuiPaiScore[constant.TingPaiType.QINGYISE].fangpao;
                            }
                        }
                    }
                }
            }
        }
        if (seatData.huType.indexOf(constant.TingPaiType.QINGYISE) == -1) {
            seatData.huType.push(constant.TingPaiType.QINGYISE);
        }
    }
}

function calTianDiHu(game, seatData) {
    if (seatData.pattern == "tianhu" || seatData.pattern == "dihu") {
        var topHu = (seatData.pattern == "tianhu") ? constant.TingPaiType.TIANHU : constant.TingPaiType.DIHU;
        for (var j = 0; j < game.gameSeats.length; ++j) {
            let seat = game.gameSeats[j];
            if (seat.seatIndex != seatData.seatIndex) {
                seat.score -= constant.HuiPaiScore[topHu].zimo;
                log.debug("user id == " + seat.userId + " seat score == " + seat.score);
                seat.huScore -= constant.HuiPaiScore[topHu].zimo;
            }
        }
        seatData.score += ((game.gameSeats.length - 1) * constant.HuiPaiScore[topHu].zimo);
        seatData.huScore += ((game.gameSeats.length - 1) * constant.HuiPaiScore[topHu].zimo);
        log.debug("user id == " + seatData.userId + " seatData score == " + seatData.score);
        if (seatData.huType.indexOf(topHu) == -1) {
            seatData.huType.push(topHu);
        }
    }
}

function isGangShangHua(game, seatData) {
    for (var k = seatData.actions.length - 1; k >= 0; --k) {
        var action = seatData.actions[k];
        if (action.type == constant.TingPaiType.GANGSHANGHUA) {
            if (seatData.huType.indexOf(constant.TingPaiType.GANGSHANGHUA) == -1) {
                seatData.huType.push(constant.TingPaiType.GANGSHANGHUA);
            }
            return true;
        }
    }
    return false;
}

function calZimoDetail(game, seatData, type, special_type) {
    for (var j = 0; j < game.gameSeats.length; ++j) {
        let seat = game.gameSeats[j];
        if (seat.seatIndex != seatData.seatIndex) {
            if (special_type == 1) {
                seat.score -= (constant.HuiPaiScore[constant.TingPaiType.GANGSHANGHUA].zimo + constant.HuiPaiScore[type].zimo);
                seat.huScore -= (constant.HuiPaiScore[constant.TingPaiType.GANGSHANGHUA].zimo + constant.HuiPaiScore[type].zimo);
            }
            else {
                seat.score -= constant.HuiPaiScore[type].zimo;
                seat.huScore -= constant.HuiPaiScore[type].zimo;
            }
            log.debug("user id == " + seat.userId + " seat score == " + seat.score);
        }
    }
    if (special_type == 1) {
        seatData.score += ((game.gameSeats.length - 1) * (constant.HuiPaiScore[constant.TingPaiType.GANGSHANGHUA].zimo + constant.HuiPaiScore[type].zimo));
        seatData.huScore += ((game.gameSeats.length - 1) * (constant.HuiPaiScore[constant.TingPaiType.GANGSHANGHUA].zimo + constant.HuiPaiScore[type].zimo));
    }
    else {
        seatData.score += ((game.gameSeats.length - 1) * constant.HuiPaiScore[type].zimo);
        seatData.huScore += ((game.gameSeats.length - 1) * constant.HuiPaiScore[type].zimo);
    }

    if (seatData.huType.indexOf(type) == -1) {
        seatData.huType.push(type);
    }
    log.debug("user id == " + seatData.userId + " seatData score == " + seatData.score);
}

function calZimoScore(game, seatData, type, special_type) {
    seatData.paiType = type;

    //是否杠上花 special_type 0--非特殊牌 1--杠上花 2--抢杠胡
    if (special_type == 0) {
        special_type = isGangShangHua(game, seatData);
    }

    //抢杠胡算分
    if (special_type == 2) {
        var index = seatData.huType.indexOf(constant.TingPaiType.QINGYISE);
        if (index == -1 && isQingYiSe(seatData)) {
            calQiangGangHu(game, seatData, constant.TingPaiType.QINGYISE, constant.HuiPaiScore[constant.TingPaiType.QINGYISE].zimo); //抢杠胡清一色输者按自摸独赔3家给胡牌者
        }

        //其余牌型分数
        calQiangGangHu(game, seatData, type, constant.HuiPaiScore[type].zimo);
    }
    else { //除清一色外的牌型算分
        calQingYiSe(game, seatData, 1, 0);              //不是抢杠胡按正常分数计算
        calZimoDetail(game, seatData, type, special_type);
    }
}

function addZimoScore(game, seatData) {
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat != null && seat.seatIndex != seatData.seatIndex) {
            seat.score -= 1;
            seat.huScore -= 1;
        }
    }
    seatData.score += (game.gameSeats.length - 1);
    seatData.huScore += (game.gameSeats.length - 1);
}

function isGangShangPao(game, seatData) {
    for (var k = seatData.actions.length - 1; k >= 0; --k) {
        var action = seatData.actions[k];
        if (action.type == constant.TingPaiType.GANGSHANGPAO && action.targets != null) {
            if (seatData.huType.indexOf(constant.TingPaiType.GANGSHANGPAO) == -1) {
                seatData.huType.push(constant.TingPaiType.GANGSHANGPAO);
            }
            var gangSeat = game.gameSeats[action.targets[0]];
            if (gangSeat != null) {
                log.debug("all seat ", seatData.userId, gangSeat.userId);
                delGangScoreForGangshangpao(game, seatData, gangSeat);
            }
            return 1;
        }
    }
    return 0;
}

function isQiangGangHu(game, seatData) {
    for (var k = seatData.actions.length - 1; k >= 0; --k) {
        var action = seatData.actions[k];
        if (action.type == constant.TingPaiType.QIANGGANGHU && action.targets != null) {
            if (seatData.huType.indexOf(constant.TingPaiType.QIANGGANGHU) == -1) {
                seatData.huType.push(constant.TingPaiType.QIANGGANGHU);
            }
            return 2;
        }
    }
    return 0;
}

function calQiangGangHu(game, seatData, type, addScore) {
    for (var k = seatData.actions.length - 1; k >= 0; --k) {
        var action = seatData.actions[k];
        if (action.type == constant.TingPaiType.QIANGGANGHU && action.targets != null && action.targets.length > 0) {
            var seat = game.gameSeats[action.targets[0]];
            if (seat != null) {
                seat.score -= ((game.gameSeats.length - 1) * addScore);
                seat.huScore -= ((game.gameSeats.length - 1) * addScore);
                seatData.score += ((game.gameSeats.length - 1) * addScore);
                seatData.huScore += ((game.gameSeats.length - 1) * addScore);
            }

            if (seatData.huType.indexOf(type) == -1) {
                seatData.huType.push(type);
            }
            log.debug("userId == " + seat.userId + " fang pao seat.score == " + seat.score);
            return;
        }
    }
}

function calActionDetail(game, seatData, type, addScore) {
    for (let j = 0; j < game.gameSeats.length; ++j) {
        let seat = game.gameSeats[j];
        if (seat.seatIndex != seatData.seatIndex) {
            if (seatData.pattern == "dihu") {
                seat.score -= addScore;
                seat.huScore -= addScore;
                seatData.score += addScore;
                seatData.huScore += addScore;

                if (seatData.huType.indexOf(type) == -1) {
                    seatData.huType.push(type);
                }
                log.debug("userId == " + seat.userId + " di hu seat.score == " + seat.score);
            } else {
                for (let k = seat.actions.length - 1; k >= 0; --k) {
                    let action = seat.actions[k];
                    if (action.type == "fangpao") {
                        seat.score -= addScore;
                        seat.huScore -= addScore;
                        seatData.score += addScore;
                        seatData.huScore += addScore;

                        if (seatData.huType.indexOf(type) == -1) {
                            seatData.huType.push(type);
                        }
                        log.debug("userId == " + seat.userId + " fang pao seat.score == " + seat.score);
                        return;
                    }
                }
            }
        }
    }
}

function calFangpaoScore(game, seatData, type, special_type) {
    seatData.paiType = type;

    //放炮算分
    calActionDetail(game, seatData, type, constant.HuiPaiScore[type].fangpao);

    //清一色计分
    calQingYiSe(game, seatData, 0, special_type);
}

//计算结算
function doAccount(game, roomInfo, userId, storeAllRes) {
    for (let i = 0; i < game.gameSeats.length; ++i) {
        let seat = game.gameSeats[i];
        log.debug("before calculate account, user id == " + seat.userId + " seat score == " + seat.score);
    }

    if (game.hupaiList.length == 1) {
        calSingleUserHuScore(game, userId);
        storeAllRes.winner = game.hupaiList[0];
    }
    else {
        for (let i = 0; i < game.gameSeats.length; ++i) {
            let seat = game.gameSeats[i];
            if (game.hupaiList.indexOf(seat.userId) != -1) {
                calSingleUserHuScore(game, seat.userId);
                log.debug("after calculate account, user id == " + seat.userId + " seat score == " + seat.score);
                if (storeAllRes.winner === '') {
                    storeAllRes.winner += seat.userId;
                } else {
                    storeAllRes.winner += (',' + seat.userId);
                }
            }
        }
    }
    return true;
}

function calSingleUserHuScore(game, userId) {
    //赢家位置
    var seatIndex = getSeatIndex(userId);

    //计算连庄次数
    var seatData = game.gameSeats[seatIndex];

    //天胡和地胡校验,有出一张牌了,天胡或地胡就不再是了
    if (seatData.pattern == "tianhu") {
        if (game.button == seatData.seatIndex) {
            if (seatData.folds.length > 0 || game.chuPai >= 0) {
                seatData.pattern = "";
            }
        }
        else {
            log.debug("err: you are not banker, should not hu tianhu pai!");
            seatData.pattern = "";
        }
    }
    else if (seatData.pattern == "dihu") {
        if (game.button != seatData.seatIndex) {
            if (seatData.folds.length > 0) {
                seatData.pattern = "";
            }
        }
        else {
            log.debug("err: you are not player, should not hu dihu pai!");
            seatData.pattern = "";
        }
    }

    //special_type 0--非特殊类型 2--抢杠胡
    var special_type = isQiangGangHu(game, seatData);

    //天地胡
    calTianDiHu(game, seatData);

    //区分牌型
    for (var i = 0; i < seatData.tingResult.length; i++) {
        var tingTypes = seatData.tingResult[i];
        var tingPais = tingTypes.tingPais;
        if (tingPais == null) continue;
        if (game.chuPai == INVALID_PAI) {
            var val = seatData.holds[seatData.holds.length - 1];
            if (~tingPais.indexOf(val)) {
                calZimoScore(game, seatData, tingTypes.type, special_type);
            }
        }
        else {
            if (~tingPais.indexOf(game.chuPai)) {
                calFangpaoScore(game, seatData, tingTypes.type, 0);
            }
        }
    }

    //自摸和按自摸算的牌型统计加一分
    if (game.chuPai == INVALID_PAI || seatData.pattern == "tianhu" || seatData.pattern == "dihu") {
        if (special_type != 2) {
            addZimoScore(game, seatData);       //自摸每人单给1分           
        }
        else {
            calQiangGangHu(game, seatData, constant.TingPaiType.QIANGGANGHU, 1);  //抢杠胡自摸单独增加1分
        }
    }
    else {
        isGangShangPao(game, seatData);    //杠上炮, 去掉杠分
    }

    //计算查缺分
    calQueMenScore(game, seatData);

    seatData.huType.sort(function (a, b) { return b - a; });
    return true;
}

function calQueMenScore(game, seatData) {
    for (let i = 0; i < game.gameSeats.length; ++i) {
        let seat = game.gameSeats[i];
        if (seat != null && seat.seatIndex != seatData.seatIndex) {
            seat.isQueMen = isQueMen(game, seat.seatIndex);
            if (!seat.isQueMen) {
                var payScore = getPayScore(seat);
                seatData.score += payScore;
                seat.score -= payScore;
                seatData.chaQueScore.push({
                    score: payScore,
                    seatId: seatData.seatIndex
                });
                seat.chaQueScore.push({
                    score: -1 * payScore,
                    seatId: seatData.seatIndex
                });
            }
        }
    }
}

function delGangScoreForGangshangpao(game, seatData, gangSeat) {
    if (gangSeat.gangs.length > 0) {
        var pai = gangSeat.gangs[gangSeat.gangs.length - 1].pai;
        var type = gangSeat.gangs[gangSeat.gangs.length - 1].type;
        if (game.noScoreGang.indexOf(pai) != -1) {
            switch (type) {
                case constant.GangPaiType.MINGGANG: {
                    if (gangSeat.diangangScore != null && gangSeat.diangangScore[pai] != null) {
                        gangSeat.diangangScore[pai] = null;
                    }
                    if (seatData.diangangScore != null && seatData.diangangScore[pai] != null) {
                        seatData.diangangScore[pai] = null;
                    }
                    break;
                }
                case constant.GangPaiType.ANGANG: {
                    if (gangSeat.angangScore != null && gangSeat.angangScore[pai] != null) {
                        gangSeat.angangScore[pai] = null;
                    }
                    if (seatData.angangScore != null && seatData.angangScore[pai] != null) {
                        seatData.angangScore[pai] = null;
                    }
                    break;
                }
                case constant.GangPaiType.WANGANG: {
                    if (gangSeat.wangangScore != null && gangSeat.wangangScore[pai] != null) {
                        gangSeat.wangangScore[pai] = null;
                    }
                    if (seatData.wangangScore != null && seatData.wangangScore[pai] != null) {
                        seatData.wangangScore[pai] = null;
                    }
                    break;
                }
            }
        }
    }
}

function calGangScore(game) {
    //赢家和其余3家计算得分
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat == null) {
            continue;
        }

        if (!isQueMen(game, seat.seatIndex)) {  // 不que
            continue;
        }

        Object.keys(seat.angangScore).forEach(function (pai) {
            if (seat.angangScore[pai] != null && seat.seatIndex == seat.angangScore[pai].seatId) {
                seat.gangScore += (3 * (game.gameSeats.length - 1));
                seat.score += (3 * (game.gameSeats.length - 1));
                log.debug("seat an gang score ", seat.userId, seat.gangScore, seat.score);
                for (let j = 0; j < game.gameSeats.length; ++j) {
                    let eachSeat = game.gameSeats[j];
                    if (eachSeat != null && eachSeat.seatIndex != seat.seatIndex && eachSeat.angangScore[pai] != null && seat.seatIndex == eachSeat.angangScore[pai].seatId) {
                        eachSeat.gangScore -= 3;
                        eachSeat.score -= 3;
                        log.debug("each seat an gang score ", eachSeat.userId, eachSeat.gangScore, eachSeat.score);
                    }
                }
            }
        });

        Object.keys(seat.diangangScore).forEach(function (pai) {
            if (seat.diangangScore[pai] != null && seat.seatIndex == seat.diangangScore[pai].seatId) {
                seat.gangScore += 2;
                seat.score += 2;
                log.debug("seat dian gang score ", seat.userId, seat.gangScore, seat.score);
                for (let j = 0; j < game.gameSeats.length; ++j) {
                    let eachSeat = game.gameSeats[j];
                    if (eachSeat != null && eachSeat.seatIndex != seat.seatIndex && eachSeat.diangangScore[pai] != null && seat.seatIndex == eachSeat.diangangScore[pai].seatId) {
                        eachSeat.gangScore -= 2;
                        eachSeat.score -= 2;
                        log.debug("each seat dian gang score ", eachSeat.userId, eachSeat.gangScore, eachSeat.score);
                    }
                }
            }
        });

        Object.keys(seat.wangangScore).forEach(function (pai) {
            if (seat.wangangScore[pai] != null && seat.seatIndex == seat.wangangScore[pai].seatId) {
                seat.gangScore += (4 * (game.gameSeats.length - 1));
                seat.score += (4 * (game.gameSeats.length - 1));
                log.debug("seat wan gang score ", seat.userId, seat.gangScore, seat.score);
                for (let j = 0; j < game.gameSeats.length; ++j) {
                    let eachSeat = game.gameSeats[j];
                    if (eachSeat != null && eachSeat.seatIndex != seat.seatIndex && eachSeat.wangangScore[pai] != null && seat.seatIndex == eachSeat.wangangScore[pai].seatId) {
                        eachSeat.gangScore -= 4;
                        eachSeat.score -= 4;
                        log.debug("each seat wan gang score ", eachSeat.userId, eachSeat.gangScore, eachSeat.score);
                    }
                }
            }
        });

        log.debug("userId == " + seat.userId + " losser score == " + seat.score);
    }
}

//串行
function seriesSaveToDB(game, roomInfo, dbresult, roomId, data, userId) {
    async.series({
        store: function (callback) {
            //创建游戏
            setTimeout(function () {
                var strActions = JSON.stringify(game.actionList);
                construct_game_base_info(game);
                db.store_game_round_result(game.roomInfo.uuid, game.gameIndex, game.baseInfoJson, strActions, dbresult);
            }, 500);

            callback(null, 1);
        },
        update_gems: function (callback) {
            //广播当局结束
            data.isend = (roomInfo.numOfGames >= roomInfo.conf.maxGames) ? true : false;

            if (data.isend) {
                //发送每局结果
                getFinallyOverData(data, roomInfo, userId);
            }
            roomRes[roomId] = data;
            roomRes[roomId].isSend = false;
            roomRes[roomId].saveTime = Date.now();
            userMgr.broadcastInRoom('game_over_push', data, userId, true);
            log.debug("ju shu == " + roomInfo.numOfGames + " isend == " + data.isend);

            //如果是第一次，并且不是强制解散 则扣除房卡
            if (roomInfo.numOfGames == 1 && roomInfo.conf.maxGames == 4) {
                db.cost_gems(game.gameSeats[0].userId, 2);
            }
            else if (roomInfo.numOfGames == 1 && roomInfo.conf.maxGames == 8) {
                db.cost_gems(game.gameSeats[0].userId, 3);
            }
            clearDissolvingList(roomId);
            callback(null, 2);
        },
        release: function (callback) {
            //是否满足局数限制解散房间
            if (data.isend) {
                //如果局数已够，则进行整体结算，并关闭房间
                setTimeout(function () {
                    userMgr.kickAllInRoom(roomId);
                    roomMgr.destroy(roomId);
                    db.archive_games(roomInfo.uuid);
                }, 1500);
            }
        }
    }, function (err, results) {
        log.debug(results);
    });
}

//清空解散列表
function clearDissolvingList(roomId) {
    for (let i = dissolvingList.length - 1; i >= 0; --i) {
        if (dissolvingList[i] == roomId) {
            dissolvingList.splice(i, 1);
        }
    }
}

function sendEachRoundRes(roomInfo) {
    var round_data = [];
    for (var i = 0; i < roomInfo.seats.length; ++i) {
        var seat = roomInfo.seats[i]
        if (seat.roundScore.length > 0) {
            round_data.push({
                userId: seat.userId,
                roundScore: seat.roundScore,
            });
        }
    }
    if (roomInfo.seats.length > 0) {
        userMgr.broadcastInRoom('round_score', round_data, roomInfo.seats[0].userId, true);
    }
}

function getPayScore(seatData) {
    var payScore = 0;
    if (!seatData.isTinged && !seatData.isQueMen) {
        if (seatData.que != INVALID_PAI) {
            for (let key in seatData.countMap) {
                let pai = Number.parseInt(key);
                if (pai >= ((seatData.que - 1) * 9) && pai < (seatData.que * 9)) {
                    payScore += seatData.countMap[pai];
                }
            }
        }
        log.debug("one unting man cha que pay score == ", seatData.userId, payScore);
    }
    return payScore;
}

function isQueMen(game, seatIndex) {
    var bQue = true;
    var seatData = game.gameSeats[seatIndex];
    for (let j = 0; j < seatData.holds.length; ++j) {
        let pai = seatData.holds[j];
        if (getMJType(pai) === seatData.que) {
            return false;
        }
    }
    return bQue;
}

//黄庄根据叫牌情况算出庄家
function calZhuangByhuangZhuang(game, roomInfo) {
    //去掉正常胡牌多算的平胡
    for (let i = 0; i < game.gameSeats.length; ++i) {
        let seat = game.gameSeats[i];
        if (seat != null) {
            checkTingResultInHuangzhuang(seat);
        }
    }

    //计算黄庄分
    var unTingArr = findUnTingedPlayers(game);
    log.debug("calZhuangByhuangZhuang not ting pai num == unTingArr index == ", unTingArr.length, unTingArr);
    if (unTingArr.length > 0 && unTingArr.length < game.gameSeats.length) {
        if (unTingArr.length == 1)  //一人未叫牌
        {
            roomInfo.nextButton = unTingArr[0];
            var seatData = game.gameSeats[unTingArr[0]];
            if (seatData != null) {
                seatData.isTinged = false;
                seatData.isQueMen = isQueMen(game, unTingArr[0]);
                log.debug("user id == " + seatData.userId + " que status == " + seatData.isQueMen);
                let payScore = getPayScore(seatData);
                log.debug("1 pay score == ", payScore);
                calScoreByHuangzhuang(game, seatData, payScore);
            }
        }
        else {
            var bHasZhuang = false;  //庄家是否没叫牌
            for (var i = 0; i < unTingArr.length; ++i) {
                if (unTingArr[i] == game.button) {
                    bHasZhuang = true;
                }
                var seatData = game.gameSeats[unTingArr[i]];
                if (seatData != null) {
                    seatData.isTinged = false;
                    seatData.isQueMen = isQueMen(game, unTingArr[i]);
                    log.debug("user id == " + seatData.userId + " que status == " + seatData.isQueMen);
                    let payScore = getPayScore(seatData);
                    log.debug("2 pay score == ", payScore);
                    calScoreByHuangzhuang(game, seatData, payScore);
                }
            }
            if (bHasZhuang)  //庄家没叫牌,轮到庄家的下家作庄
            {
                roomInfo.nextButton = (game.button + 1) % game.gameSeats.length;
            }
        }
    }
}

//黄庄算分 seatData 为未听牌者
function calScoreByHuangzhuang(game, seatData, payScore) {
    var fangpaoScore = 0;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seat = game.gameSeats[i];
        if (seat != null && seat.seatIndex != seatData.seatIndex) {
            if (isTinged(seat)) {
                //查叫未听牌者给听牌者累加胡牌分
                var bQingyise = isQingYiSe(seat);
                if (bQingyise) {
                    fangpaoScore = constant.HuiPaiScore[constant.TingPaiType.QINGYISE].fangpao;

                    log.debug("ting pai get fang pao score == " + fangpaoScore);
                    seatData.tingScore -= fangpaoScore;
                    seat.tingScore += fangpaoScore;
                    seatData.score -= fangpaoScore;
                    seat.score += fangpaoScore;
                }
                seat.tingResult.forEach(function (tingPai) {
                    log.debug("check ting, ting pai type == " + tingPai.type);
                    if (tingPai.type < 7) {
                        fangpaoScore = constant.HuiPaiScore[tingPai.type].fangpao;

                        log.debug("ting pai get fang pao score == " + fangpaoScore);
                        seatData.tingScore -= fangpaoScore;
                        seat.tingScore += fangpaoScore;
                        seatData.score -= fangpaoScore;
                        seat.score += fangpaoScore;
                    }
                    seat.isQueMen = true;
                });
            }
            if (!seatData.isQueMen) {
                //查缺未缺门牌者给听牌者缺门色的张数分
                if (isTinged(seat)) {
                    log.debug("before calculate seatData and seat score == ", seatData.userId, seatData.score, seat.userId, seat.score);
                    seatData.score -= payScore;
                    seat.score += payScore;
                    seatData.chaQueScore.push({
                        score: -1 * payScore,
                        seatId: seat.seatIndex
                    });
                    seat.chaQueScore.push({
                        score: payScore,
                        seatId: seat.seatIndex
                    });
                    log.debug("after calculate seatData and seat score == ", seatData.userId, seatData.score, seat.userId, seat.score);
                }
            }
        }
    }
}

//正常结算根据叫牌情况算出庄家
function calZhuangByNormal(userId, game, roomInfo) {
    //赢家作庄
    var winner = getSeatIndex(userId);
    if (game.hupaiList.length == 1) {
        if (winner != roomInfo.nextButton) {
            roomInfo.nextButton = winner;
        }
    }
    else  //遵义麻将新增代码 任德 begin
    {
        //一炮多响,点炮者的下家为庄家
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var seat = game.gameSeats[i];
            var bFangPao = false;
            for (var k = seat.actions.length - 1; k >= 0; --k) {
                var action = seat.actions[k];
                if (action.type == "fangpao")  //只有一个放炮的
                {
                    bFangPao = true;
                    break;
                }
            }
            if (bFangPao) {
                roomInfo.nextButton = (seat.seatIndex + 1) % game.gameSeats.length;
                break;
            }
        }
    } //遵义麻将新增代码 任德 end
}

function generateGameOverData(game, seatData, result, operStatus, storeRes) {
    var huType = {};
    var scoreList = [];
    var userInfo = {
        userId: seatData.userId,
        huType: huType,
        nickName: game.roomInfo.seats[seatData.seatIndex].name,
        scoreList: scoreList,
        holds: seatData.holds,
        totalscore: 0,
    };

    if (game.hupaiList.indexOf(seatData.userId) != -1) {
        huType.type = 0;
        huType.config = 0;
        huType.titleText = (seatData.iszimo ? "自摸 " : "") + result.huType.map(function (type) {
            return constant.TingPaiTypeDescribe[type];
        }).join(";");
        huType.score = result.huScore;
        log.debug("is fangpao ", seatData.iszimo);
    } else if (result.isTinged) {
        huType.type = 1;
        huType.config = -1;
        huType.titleText = '叫牌';
        huType.score = result.huScore;
        log.debug("fangpao==???>", result.isFangPao, seatData.userId);
        if (result.isFangPao) {
            huType.config = 1;
        }
    } else {
        huType.type = 2;
        huType.config = -1;
        huType.titleText = '没叫牌';
        huType.score = result.huScore;
        log.debug("fangpao==???>", result.isFangPao, seatData.userId);
        if (result.isFangPao) {
            huType.config = 1;
        }
    }

    if (operStatus != 1) {  // 非黄庄， 缺门，才显示 
        if (Object.keys(seatData.diangangScore).length > 0) {
            Object.keys(seatData.diangangScore).forEach(function (pai) {
                if (seatData.diangangScore[pai] != null) {
                    if (isQueMen(game, seatData.diangangScore[pai].seatId)) {
                        scoreList.push({
                            type: pai, // 点杠
                            num: 4,
                            hiddeNum: 0,
                            title: '明豆',
                            seatId: seatData.diangangScore[pai].seatId,
                            score: seatData.diangangScore[pai].score
                        });
                    }

                    if (scoreList.seatId == seatData.seatIndex) {
                        storeRes.mingGangPai = (storeRes.mingGangPai === undefined) ? (pai) : (storeRes.mingGangPai + ',' + pai);
                        storeRes.mingGangScore = (storeRes.mingGangScore === undefined) ? (seatData.diangangScore[pai].score) :
                            (storeRes.mingGangScore + seatData.diangangScore[pai].score);
                    }
                }
            });
        }

        if (Object.keys(seatData.angangScore).length > 0) {
            Object.keys(seatData.angangScore).forEach(function (pai) {
                if (seatData.angangScore[pai] != null) {
                    if (isQueMen(game, seatData.angangScore[pai].seatId)) {
                        scoreList.push({
                            type: pai, // 暗杠
                            num: 4,
                            title: '暗豆',
                            hiddeNum: 1,
                            seatId: seatData.angangScore[pai].seatId,
                            score: seatData.angangScore[pai].score
                        });
                    }
                    if (scoreList.seatId == seatData.seatIndex) {
                        storeRes.anGangPai = (storeRes.anGangPai === undefined) ? (pai) : (storeRes.anGangPai + ',' + pai);
                        storeRes.anGangScore = (storeRes.anGangScore === undefined) ? (seatData.angangScore[pai].score) :
                            (storeRes.anGangScore + seatData.angangScore[pai].score);
                    }
                }
            });
        }

        if (Object.keys(seatData.wangangScore).length > 0) {
            Object.keys(seatData.wangangScore).forEach(function (pai) {
                if (seatData.wangangScore[pai] != null) {
                    if (isQueMen(game, seatData.wangangScore[pai].seatId)) {
                        scoreList.push({
                            type: pai, // 转弯豆
                            num: 4,
                            title: '转弯豆',
                            hiddeNum: 1,
                            seatId: seatData.wangangScore[pai].seatId,
                            score: seatData.wangangScore[pai].score
                        });
                    }
                    if (scoreList.seatId == seatData.seatIndex) {
                        storeRes.wanGangPai = (storeRes.wanGangPai === undefined) ? (pai) : (storeRes.wanGangPai + ',' + pai);
                        storeRes.wanGangScore = (storeRes.wanGangScore === undefined) ? (seatData.wangangScore[pai].score) :
                            (storeRes.wanGangScore + seatData.wangangScore[pai].score);
                    }
                }
            });
        }
    }

    if (seatData.yuanQueScore !== undefined && seatData.yuanQueScore.length > 0) {
        for (let i = 0; i < seatData.yuanQueScore.length; ++i) {
            var queScore = seatData.yuanQueScore[i];
            if (queScore != null) {
                scoreList.push({
                    type: -1, // 圆缺
                    num: 0,
                    title: '圆缺',
                    seatId: queScore.seatId,
                    score: queScore.score
                });
                if (scoreList.seatId == seatData.seatIndex) {
                    storeRes.yuanQueScore = (storeRes.yuanQueScore === undefined) ? queScore.score : (storeRes.yuanQueScore + queScore.score);
                }
            }
        }
    }

    if (seatData.baoTingScore.length > 0) {
        seatData.baoTingScore.forEach(function (bTscore) {
            log.debug("baoting", bTscore);
            seatData.score += bTscore.score;
            scoreList.push({
                type: -1, // 报听
                num: 0,
                title: '报听',
                seatId: bTscore.seatId,
                score: bTscore.score
            });
        });
    }

    if (seatData.shaBaoScore.length > 0) {
        seatData.shaBaoScore.forEach(function (sBscore) {
            log.debug("shaBao", sBscore);
            seatData.score += sBscore.score;
            scoreList.push({
                type: -1, // 杀报
                num: 0,
                title: '杀报',
                seatId: sBscore.seatId,
                score: sBscore.score
            });
        });
    }

    //黄庄处理
    if (operStatus == 0 || operStatus == 1) {
        for (let i = 0; i < seatData.chaQueScore.length; ++i) {
            var chaQue = seatData.chaQueScore[i];
            if (chaQue != null) {
                scoreList.push({
                    type: -2, // 查缺
                    num: 0,
                    title: '查缺',
                    seatId: chaQue.seatId,
                    score: chaQue.score
                });
                if (scoreList.seatId == seatData.seatIndex) {
                    storeRes.chaQueScore = (storeRes.chaQueScore === undefined) ? chaQue.score : (storeRes.chaQueScore + chaQue.score);
                }
            }
        }
        if (operStatus == 1) {
            huType.score = result.tingScore;
        }
    }

    var mergeArray = function (array) {
        var objectOne = array[0];
        if (objectOne) {
            objectOne.flag = true;
        }

        for (var i = 1; i < array.length; i++) {
            var objectTwo = array[i];
            if (objectOne.type >= 0 && objectOne.type === objectTwo.type && objectOne.seatId === objectTwo.seatId) {
                objectTwo.score = objectOne.score + objectTwo.score;
                objectTwo.flag = true;
                objectOne.flag = false;
            } else {
                objectOne.flag = true;
                objectTwo.flag = true;
            }
            objectOne = objectTwo;
        }
        var newArray = [];
        for (i = 0; i < array.length; i++) {
            if (array[i].flag === true) {
                delete array[i].flag;
                newArray.push(array[i]);
            }
        }
        return newArray;
    };
    log.debug("beginjifen====shang =>", seatData.score);
    storeRes.fanJi = game.huPaiFanJi[0];
    if (operStatus === 0) {  //正确结算才允许翻鸡牌
        if (seatData.shangJiScore !== undefined) {
            log.debug("shange==================>", seatData.shangJiScore_sum);
            seatData.score += seatData.shangJiScore_sum; //累加鸡分
            seatData.shangJiScore.forEach(function (shangScore) {
                scoreList.push({
                    type: game.huPaiFanJi[1], // 上鸡
                    num: game.gameSeats[shangScore.seatId].shangJiScore_num,
                    seatId: shangScore.seatId,
                    title: '上鸡',
                    score: shangScore.score
                });
                if (shangScore.seatId == seatData.seatIndex) {
                    storeRes.shangJiNum = (storeRes.shangJiNum === undefined) ? 1 : (storeRes.shangJiNum + 1);
                    storeRes.shangJiScore = (storeRes.shangJiScore === undefined) ? shangScore.score : (storeRes.shangJiScore + shangScore.score);
                    storeRes.shangJiPai = game.huPaiFanJi[1];
                }
            });
        }
        log.debug("beginjifen====xia =>", seatData.score, game.huPaiFanJi);
        if (seatData.xiaJiScore !== undefined) {
            log.debug("xiaaa================>", seatData.xiaJiScore_sum);
            seatData.score += seatData.xiaJiScore_sum; //累加鸡分

            seatData.xiaJiScore.forEach(function (xiaScore) {
                scoreList.push({
                    type: game.huPaiFanJi[2], // 上鸡
                    num: game.gameSeats[xiaScore.seatId].xiaJiScore_num,
                    seatId: xiaScore.seatId,
                    title: '下鸡',
                    score: xiaScore.score
                });
                if (xiaScore.seatId == seatData.seatIndex) {
                    storeRes.xiaJiNum = (storeRes.xiaJiNum === undefined) ? 1 : (storeRes.xiaJiNum + 1);
                    storeRes.xiaJiScore = (storeRes.xiaJiScore === undefined) ? xiaScore.score : (storeRes.xiaJiScore + xiaScore.score);
                    storeRes.xiaJiPai = game.huPaiFanJi[2];
                }
            });
        }
    }

    //累计得普通鸡分
    var JpaiNameMap = constant.JpaiNameMap;
    log.debug("beginjifen====>", seatData.score);
    if (game.jiPaiList.length > 0) {
        game.jiPaiList.forEach(function (pai) {
            //log.debug('fff', seatData.userId, seatData.seatIndex, seatData[JpaiNameMap[pai]], seatData[JpaiNameMap[pai] + '_sum']);
            if (seatData[JpaiNameMap[pai] + '_sum'] !== undefined) {
                seatData.score += seatData[JpaiNameMap[pai] + '_sum']; //累加鸡分
                seatData[JpaiNameMap[pai]].sort(function (a, b) {
                    return a.seatId - b.seatId;
                });
                seatData[JpaiNameMap[pai]].forEach(function (jiPaiScore) {
                    var jiType = constant.getJiPaiTypeByPai(pai);
                    scoreList.push({
                        type: pai, //  
                        num: game.gameSeats[jiPaiScore.seatId][JpaiNameMap[pai] + '_num'],
                        title: constant.JiPaiDescribe[jiType],
                        seatId: jiPaiScore.seatId,
                        score: jiPaiScore.score
                    });
                    if (jiPaiScore.seatId == seatData.seatIndex) {
                        storeRes.puTongJiNum = (storeRes.puTongJiNum === undefined) ? 1 : (storeRes.puTongJiNum + 1);
                        storeRes.puTongJiScore = (storeRes.puTongJiScore === undefined) ? jiPaiScore.score : (storeRes.puTongJiScore + jiPaiScore.score);
                        storeRes.puTongJiPai = (storeRes.puTongJiPai === undefined) ? pai : (',' + storeRes.puTongJiPai);
                    }
                });
            }
        });
    }
    log.debug("endjifen====>", seatData.score);
    log.debug(scoreList);
    scoreList = mergeArray(scoreList);
    log.debug(scoreList);
    userInfo.scoreList = scoreList;
    userInfo.sumScore = seatData.score; // 输出成绩
    userInfo.huType = huType;
    return userInfo;
}

function getFinallyOverData(data, roomInfo, userId) {
    //如果局数已够,则进行整体结算,并关闭房间,发送每局结果
    var userInfoListR = [];
    var overUserInfoList = data.overs;
    for (var i = 0; i < overUserInfoList.length; i++) {
        var resultUserInfoList = {};
        var juShus = [];
        resultUserInfoList.nickName = overUserInfoList[i].nickName;
        resultUserInfoList.userId = overUserInfoList[i].userId;
        resultUserInfoList.juShus = juShus;
        var roundScore = roomInfo.seats[i].roundScore;
        var sumScore = 0;
        for (var j = 0; j < roundScore.length; j++) {
            var juShu = {
                num: j + 1,
                score: roundScore[j]
            };
            sumScore += roundScore[j];
            juShus.push(juShu);
        }
        resultUserInfoList.scoreAll = sumScore;
        userInfoListR.push(resultUserInfoList);
    }
    data.results = userInfoListR;
    userMgr.broadcastInRoom('game_over_push', data, userId, true);
}

function getSeatsIdByUsersId(game, usersId) {
    var SeatsId = usersId.map(function (userId) {
        return _.find(game.gameSeats, function (seatData) { return seatData.userId === userId; }).seatIndex;
    });
    return SeatsId;
}

function calBaoTingScore(game) {

    var huPaiBaoTingScore = function () {
        var calScore = function (huPaiSeatsId, seatData) {
            huPaiSeatsId.forEach(function (seatIndex) {  // 存在胡牌的玩家
                if (!~huPaiSeatsId.indexOf(seatData.seatIndex)) { //报听了， 不是胡牌的玩家  杀报得分
                    if (seatData.acChuPaiStatus) {
                        game.gameSeats[seatIndex].shaBaoScore.push({ seatId: seatIndex, score: 10 }); //给胡牌玩家杀报10分
                        seatData.shaBaoScore.push({ seatId: seatIndex, score: -10 }); //报听玩家扣十分
                        if (game.gameSeats[seatIndex].acChuPaiStatus) { // 胡牌的玩家也报听了，要给报听分
                            game.gameSeats[seatIndex].baoTingScore.push({ seatId: seatIndex, score: 10 });
                            seatData.baoTingScore.push({ seatId: seatIndex, score: -10 });
                        }
                    } else {  // //报听得分
                        if (game.gameSeats[seatIndex].acChuPaiStatus) {  // 胡牌的玩家也报听了
                            game.gameSeats[seatIndex].baoTingScore.push({ seatId: seatIndex, score: 10 });
                            seatData.baoTingScore.push({ seatId: seatIndex, score: -10 });
                        }
                    }
                }
            });
        }

        var getFangPaoPlayer = function (gameSeats) {
            var seatIndex = -1;
            for (var i = 0; i < gameSeats.length; i++) {
                var sd = gameSeats[i];
                for (var k = sd.actions.length - 1; k >= 0; --k) {
                    var action = sd.actions[k];
                    if (action.type == "fangpao") {
                        seatIndex = sd.seatIndex;
                        return seatIndex;
                    }
                }
            }
            return seatIndex;
        }

        //是正常胡牌
        var huPaiSeatsId = getSeatsIdByUsersId(game, game.hupaiList);
        var fangPaoseatIndex = getFangPaoPlayer(game.gameSeats);
        if (fangPaoseatIndex !== -1) { //存在放炮的情况
            for (var i = 0; i < game.gameSeats.length; i++) {
                if (fangPaoseatIndex === i || game.gameSeats[i].acChuPaiStatus) {
                    calScore(huPaiSeatsId, game.gameSeats[i]);
                }
            }
        } else {  //自摸的情况
            for (var i = 0; i < game.gameSeats.length; i++) { //自摸的情况  自摸要计算多个人
                var seatData = game.gameSeats[i];
                calScore(huPaiSeatsId, seatData);
            }
        }
    }

    var huangZhuangBaoTingScore = function () {

        var calScore = function (seatData) {
            for (var i = 0; i < game.gameSeats.length; i++) {
                if (game.gameSeats[i].seatIndex !== seatData.seatIndex && game.gameSeats[i].acChuPaiStatus) { //给有报听的分
                    game.gameSeats[i].baoTingScore.push({ seatId: game.gameSeats[i].seatIndex, score: 10 });
                    seatData.baoTingScore.push({ seatId: game.gameSeats[i].seatIndex, score: -10 });
                }
            }
        }

        for (var i = 0; i < game.gameSeats.length; i++) {
            var isTingPai = (isTinged(game.gameSeats[i]) > 0) ? true : false;
            if (!isTingPai) { //未听牌才计算
                calScore(game.gameSeats[i]);
            }
        }

    }
    if (game.hupaiList.length > 0) { //正确胡牌情况
        huPaiBaoTingScore();
    } else {  //黄庄情况
        huangZhuangBaoTingScore();
    }

    // log.debug('kkk', game.gameSeats[0].baoTingScore);
    // log.debug('kkk', game.gameSeats[1].baoTingScore);
}

//游戏结算 operStatus --操作状态, 0--点击胡牌后进入 1--黄庄情况下结算 2--强制解散
function doGameOver(game, userId, operStatus) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return;
    }
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }

    var userInfoList = [];
    var data = {
        isend: roomInfo.numOfGames == roomInfo.conf.maxGames,
        zhuangId: 0,
        fanId: 0,
        createRoomUserId: 0,
        overs: userInfoList,        //小结算
    };

    var storeAllRes = {
        res: [],
        userList: '',
        scoreList: '',
        winner: '',
        fangpaoUser: 0
    };

    if (game != null && game.gameSeats != null) {
        data.zhuangId = game.gameSeats[game.button].userId;
        data.createRoomUserId = game.gameSeats[0].userId;
        log.debug("game hu pai fan ji ", game.huPaiFanJi);
        data.fanId = (game.huPaiFanJi === undefined) ? 0 : game.huPaiFanJi[0];
    }

    var dbresult = [0, 0, 0, 0];
    var fnNoticeResult = function () {
        if (game == null) {
            var roomData = roomRes[roomId];
            if (roomData != null) {
                roomData.isend = true;
                getFinallyOverData(roomData, roomInfo, userId);
                userMgr.broadcastInRoom('game_over_push', roomData, userId, true);
                roomData.isSend = true;
            }
        } else {
            data.isend = true;
            getFinallyOverData(data, roomInfo, userId);
            roomRes[roomId] = data;
            roomRes[roomId].isSend = false;
            roomRes[roomId].saveTime = Date.now();
        }

        clearDissolvingList(roomId);
        //释放房间
        setTimeout(function () {
            userMgr.kickAllInRoom(roomId);
            roomMgr.destroy(roomId);
            db.archive_games(roomInfo.uuid);
        }, 1500);
    }

    if (game != null) {
        game.state = constant.GameStatus.GAMEOVER;
        userMgr.broadcastInRoom('game_status_push', { state: game.state }, userId, true);

        //计算结果
        var bHuSucc = false;
        var seatIndex = getSeatIndex(userId);
        var seatData = game.gameSeats[seatIndex];
        if (operStatus != 2) {
            //黄庄计算庄家
            if (operStatus == 1) {
                calZhuangByhuangZhuang(game, roomInfo);
                bHuSucc = true;
            }
            else //正常结算计算庄家
            {
                bHuSucc = doAccount(game, roomInfo, userId, storeAllRes);
                calZhuangByNormal(userId, game, roomInfo);
                calGangScore(game);
            }
            calBaoTingScore(game); //计算报听分数
            for (var i = 0; i < roomInfo.seats.length; ++i) {
                var sd = game.gameSeats[i];
                sd.isTing = isTinged(sd);
            }
            _Jipai.calculationJiPaiRecordScore(game, roomInfo); // 计算鸡牌分数
            db.update_next_button(roomId, roomInfo.nextButton);
        }

        //赢家
        var seatIndex = getSeatIndex(userId);
        for (var i = 0; i < roomInfo.seats.length; ++i) {
            var rs = roomInfo.seats[i];
            var sd = game.gameSeats[i];
            var results_data = {};
            var storeRes = {};
            results_data.userId = sd.userId;
            results_data.actions = sd.actions;
            if (storeAllRes.userList === '') {
                storeAllRes.userList = sd.userId;
            } else {
                storeAllRes.userList += (',' + sd.userId);
            }

            if (operStatus != 2) {
                for (var k = sd.actions.length - 1; k >= 0; --k) {
                    var action = sd.actions[k];
                    if (action.type == "fangpao") {
                        results_data.isFangPao = true;  //谁放炮
                        storeAllRes.fangpaoUser = results_data.userId;
                        break;
                    }
                }

                results_data.huType = sd.huType;                                                //胡牌型
                results_data.huScore = sd.huScore;                                              //胡牌分数
                results_data.score = sd.score;                                                  //总计
                results_data.diangangScore = sd.diangangScore;                                  //点杠 key--杠牌 val--得分
                results_data.angangScore = sd.angangScore;                                      //暗杠 key--杠牌 val--得分
                results_data.wangangScore = sd.wangangScore;                                    //弯杠 key--杠牌 val--得分
                results_data.isYuanQue = sd.isYuanQue;                                          //是否圆缺    
                results_data.yuanQueScore = sd.yuanQueScore;                                    //圆缺得分
                results_data.isTinged = (isTinged(sd) > 0) ? true : false;                      //是否叫牌(黄庄时才有)
                results_data.tingScore = sd.tingScore;                                          //叫牌得分(黄庄时才有)
                results_data.isQueMen = sd.isQueMen;                                            //是否缺门(黄庄时才有)
                results_data.totalscore = rs.score;
            }
            else {
                results_data.isFangPao = false;
                results_data.huType = [];
                results_data.huScore = 0;
                results_data.score = 0;
                results_data.diangangScore = {};
                results_data.angangScore = {};
                results_data.wangangScore = {};
                results_data.isYuanQue = false;
                results_data.yuanQueScore = {};
                results_data.isTinged = false;
                results_data.tingScore = 0;
                results_data.isQueMen = false;
                results_data.totalscore = 0;
            }
            var result = generateGameOverData(game, sd, results_data, operStatus, storeRes);
            log.debug("result=", result);
            userInfoList.push(result);
            rs.ready = false;
            rs.score += result.sumScore;
            result.totalscore = rs.score;
            rs.roundScore.push(result.sumScore);
            if (typeof (result.sumScore) !== "number") {
                result.sumScore = 0;
            }
            dbresult[i] = result.sumScore;
            if (storeAllRes.scoreList === '') {
                storeAllRes.scoreList = result.sumScore;
            } else {
                storeAllRes.scoreList += (',' + result.sumScore);
            }
            storeAllRes.res.push(storeRes);
            delete gameSeatsOfUsers[sd.userId];
        }
        delete games[roomId];
    }

    //更新战绩
    db.update_each_round_history(roomInfo, dbresult);

    if (operStatus == 2 || game == null) {
        log.debug("force game end");
        fnNoticeResult();
    }
    else {
        //保存每局结果
        db.store_eachround_result(game, roomInfo, storeAllRes);

        //保存游戏
        seriesSaveToDB(game, roomInfo, dbresult, roomId, data, userId);
    }
}

function recordUserAction(game, seatData, type, target) {
    var d = { type: type, targets: [] };
    if (target != null) {
        if (typeof (target) == 'number') {
            d.targets.push(target);
        }
        else {
            d.targets = target;
        }
    }
    else {
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var s = game.gameSeats[i];
            if (i != seatData.seatIndex && s.hued == false) {
                d.targets.push(i);
            }
        }
    }

    seatData.actions.push(d);
    return d;
}

function recordGameAction(game, si, action, pai) {
    game.actionList.push(si);
    game.actionList.push(action);
    if (pai != null) {
        game.actionList.push(pai);
    }
}

exports.reconnectOK = function (userId) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return;
    }
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }

    var data = {
        userid: userId,
        online: true
    };

    //通知房间内其它玩家
    userMgr.broadcastInRoom('user_state_push', data, userId, true);

    var game = games[roomId];
    if (game == null) {
        var typeInfo = constant.CreateRoomInfo.type[roomInfo.conf.type];
        if (roomInfo.seats.length == typeInfo.seatlen) {
            var seats = [];
            var flag = false;
            var ready = false;
            for (var i = 0; i < roomInfo.seats.length; ++i) {
                var seat = roomInfo.seats[i];

                seats.push({
                    userid: seat.userId,
                    ip: seat.ip,
                    score: seat.score,
                    name: seat.name,
                    online: userMgr.isOnline(seat.userId),
                    ready: seat.ready,
                    seatindex: i
                });
                if (seat.ready == false || userMgr.isOnline(seat.userId) == false) {
                    flag = true;
                }
                if (seat.userId == userId) {
                    ready = seat.ready;
                }
            }

            //通知前端
            if (false == ready && roomRes[roomId] != null) {
                var data = roomRes[roomId];
                data.isSend = true;
                userMgr.sendMsg(userId, 'game_over_push', data);
            }
            else {
                var ret = {
                    roomid: roomInfo.id,
                    conf: roomInfo.conf,
                    numofgames: roomInfo.numOfGames,
                    seats: seats
                };
                userMgr.sendMsg(userId, 'reconnect_prepare_push', ret);
            }

            if (flag) {
                return;
            }

            //4个人到齐了，并且都准备好了，则开始新的一局
            exports.begin(roomId);
        }
    }
    else {
        roomMgr.setReady(userId, true);
        var numOfMJ = game.mahjongs.length - game.currentIndex;
        var gameData = {
            state: game.state,
            numofmj: numOfMJ,
            button: game.button,
            turn: game.turn,
            chuPai: game.chuPai,
            caishen: game.caishenPai,
            dishu: game.dishu,
            paidun: game.paidun,
            roomid: roomInfo.id,
            conf: roomInfo.conf,
            numofgames: roomInfo.numOfGames,
            showPai: (game.chuPaiData == null) ? {} : game.chuPaiData,
        };
        log.debug("game round == " + roomInfo.numofgames + " game state == " + game.state);
        gameData.seats = [];
        var seatData = null;
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var gameSeat = game.gameSeats[i];
            var rs = roomInfo.seats[i];
            if (i == game.button && constant.GameStatus.IDLE == game.state) {
                game.canChuPai = true;
            }

            var seat = {
                userid: gameSeat.userId,
                folds: gameSeat.lestFolds,
                chis: gameSeat.chis,
                que: gameSeat.que,
                angangs: gameSeat.angangs,
                diangangs: gameSeat.diangangs,
                wangangs: gameSeat.wangangs,
                pengs: gameSeat.pengs,
                //youxian: gameSeat.youxianChuFengPai,
                hued: gameSeat.hued,
                iszimo: gameSeat.iszimo,
                baoTingStatus: gameSeat.acChuPaiStatus,
                ip: rs.ip,
                score: rs.score,
                name: rs.name,
                online: userMgr.isOnline(rs.userId),
                ready: rs.ready,
                seatindex: i,
                jiPaiTypes: _Jipai.getZenRenJiBySeatIndex(game, gameSeat.seatIndex)
            };
            if (gameSeat.userId == userId) {
                seat.holds = gameSeat.holds;
                seatData = gameSeat;
            }
            if (gameSeat.que == INVALID_PAI) {
                //通知准备定缺
                userMgr.sendMsg(gameSeat.userId, 'game_dingque_push');
            }
            gameData.seats.push(seat);
        }

        log.debug(gameData);

        //同步整个信息给客户端
        userMgr.sendMsg(userId, 'reconnect_ok_push', gameData);
        sendOperations(game, seatData, game.chuPai);

        //发送解散数据
        sendDissolvingMsg(userId, roomId, roomInfo);

        //没有吃碰杠,你已摸牌情况下
        if (!hasOperations(seatData) && game.turn == seatData.seatindex) {
            //通知前端新摸的牌
            if (seatData.holds.length % 3 == 2) {
                userMgr.sendMsg(userId, 'game_mopai_push', { holds: seatData.holds, baoTingStatus: seatData.acChuPaiStatus });
                userMgr.broadcastInRoom('mopai_push', pai, userId, false);
                log.debug("userId == " + userId + " mo pai == " + pai);
            }
        }
    }
}

exports.setReady = function (userId) {
    var roomId = roomMgr.getUserRoom(userId);
    if (roomId == null) {
        return;
    }
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return;
    }

    roomMgr.setReady(userId, true);
    db.get_user_isConfig_pai(userId, function (isConfigPai) {
        roomMgr.setConfigPaiStatus(userId, isConfigPai);
        var game = games[roomId];
        if (game == null) {
            var typeInfo = constant.CreateRoomInfo.type[roomInfo.conf.type];
            if (roomInfo.seats.length == typeInfo.seatlen) {
                for (var i = 0; i < roomInfo.seats.length; ++i) {
                    var seat = roomInfo.seats[i];
                    if (seat.ready == false || userMgr.isOnline(seat.userId) == false) {
                        return;
                    }
                }
                //4个人到齐了，并且都准备好了，则开始新的一局
                exports.begin(roomId);
            }
        }
        else {
            //游戏状态推送
            userMgr.broadcastInRoom('game_status_push', { state: game.state }, userId, true);
            var numOfMJ = game.mahjongs.length - game.currentIndex;
            var remainingGames = roomInfo.conf.maxGames - roomInfo.numOfGames;

            var gameData = {
                state: game.state,
                numofmj: numOfMJ,
                button: game.button,
                turn: game.turn,
                chuPai: game.chuPai,
                caishen: game.caishenPai,
                dishu: game.dishu,
                paidun: game.paidun,
                showPai: (game.chuPaiData == null) ? {} : game.chuPaiData,
            };

            gameData.seats = [];
            var seatData = null;
            for (var i = 0; i < game.gameSeats.length; ++i) {
                var gameSeat = game.gameSeats[i];
                if (i === game.button && constant.GameStatus.IDLE === game.state) {
                    log.debug("game button is " + game.button);
                    game.canChuPai = true;
                }

                //   findoutYouXianFengPai(game,gameSeat);
                var seat = {
                    userid: gameSeat.userId,
                    folds: gameSeat.lestFolds,
                    chis: gameSeat.chis,
                    angangs: gameSeat.angangs,
                    diangangs: gameSeat.diangangs,
                    wangangs: gameSeat.wangangs,
                    pengs: gameSeat.pengs,
                    //youxian: gameSeat.youxianChuFengPai,
                    hued: gameSeat.hued,
                    iszimo: gameSeat.iszimo,
                    que: gameSeat.que,
                    baoTingStatus: gameSeat.acChuPaiStatus,
                    jiPaiTypes: _Jipai.getZenRenJiBySeatIndex(game, gameSeat.seatIndex)
                }
                if (gameSeat.userId == userId) {
                    seat.holds = gameSeat.holds;
                    seatData = gameSeat;
                }
                gameData.seats.push(seat);

                var data = {
                    userid: gameSeat.userId,
                    online: userMgr.isOnline(gameSeat.userId),
                };
                //通知房间内其它玩家
                userMgr.broadcastInRoom('user_state_push', data, userId, true);
            }

            //同步整个信息给客户端
            userMgr.sendMsg(userId, 'game_sync_push', gameData);

            //定缺
            (function () {
                setTimeout(function () {
                    if (seatData.que == INVALID_PAI) {
                        //通知准备定缺
                        userMgr.sendMsg(seatData.userId, 'game_dingque_push');
                    }
                    //sendOperations(game, seatData, game.chuPai);

                    //发送解散数据
                    sendDissolvingMsg(userId, roomId, roomInfo);

                }, 1500);
            })(game, seatData);
        }
    });
};

//发送解散通知
function sendDissolvingMsg(userId, roomId, roomInfo) {
    if (dissolvingList.indexOf(roomId) != -1) {
        if (roomInfo != null && roomInfo.dr != null) {
            var dr = roomInfo.dr;
            if (dr.endTime != null) {
                var ramaingTime = (dr.endTime - Date.now()) / 1000;
                var data = {
                    time: ramaingTime,
                    states: dr.states
                }
                console.log("set ready game_sync_push dissolve_notice_push release room ", userId, roomId);
                userMgr.broadcastInRoom('dissolve_notice_push', data, userId, true);
            }
        }
    }
}

exports.get_action = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData != null) {
        var game = seatData.game;
        if (game != null) {
            sendOperations(game, seatData, game.chuPai);
        }
    }
}

/**
 * 检测听牌后如果有杠，返回杠后的听牌结果比如的状态
 * 
 * @param {object} game 
 * @param {object} seatData 
 * @param {int} targetPai 
 * @returns  {bool} status
 */
function checkCompareAfterGangBaoTingResult(game, seatData, pai, anGangPai) {
    //不是牌听的状态，
    if (!seatData.acChuPaiStatus) {
        return false;
    }
    // 不能杠。
    if (!seatData.canGang) {
        return false;
    }
    var targetPai = anGangPai;
    if (seatData.countMap[targetPai] === 3) {
        targetPai = pai;
    }

    // 移除杠牌
    log.debug("比较之前的牌==>", seatData.holds, seatData.countMap[targetPai], targetPai);
    var removePais = _.remove(seatData.holds, function (n) {
        return n === targetPai;
    });
    seatData.countMap[targetPai] = 0;

    log.debug("比较之前移除后的牌==>", seatData.holds, seatData.countMap[targetPai]);

    // 听牌
    var openChuPai = [];
    var addPais = seatData.angangs.concat(seatData.diangangs).concat(seatData.wangangs).concat(seatData.pengs);
    addPais.push(targetPai);
    addPais.forEach(function (pai) {
        openChuPai = openChuPai.concat([pai, pai, pai]);
    });
    var mdeck = new hupai.MDeck();
    mdeck.combineDeck(seatData.holds).pushOperPai(openChuPai);

    if (mdeck.length() % 3 === 1) {  // 听牌必须是能被3整除余1
        seatData.tempBaoTingTingResultInGangDown = mdeck.tingPai();
    } else {
        seatData.tempBaoTingTingResultInGangDown = [];
    }

    // 还原牌
    seatData.holds = seatData.holds.concat(new Array(removePais.length).fill(targetPai));
    seatData.countMap[targetPai] = removePais.length;
    log.debug("比较之前还原后的牌==>", seatData.holds, seatData.countMap[targetPai]);

    log.debug("比较的数据==>", seatData.tempBaoTingTingResultInGangDown, seatData.tingResult);

    var status = false;
    // 比较听牌是否相等
    if (seatData.tempBaoTingTingResultInGangDown.length === seatData.tingResult.length) {
        status = _.isEqual(seatData.tempBaoTingTingResultInGangDown, seatData.tingResult);
    }
    return status;
}

function delChuPaiFromFolds(game, pai) {
    //吃碰或点杠后去掉出牌人的出牌记录
    var chuPaiSeat = game.gameSeats[game.turn];
    if (chuPaiSeat != null && chuPaiSeat.lestFolds != null) {
        var index = chuPaiSeat.lestFolds.indexOf(pai);
        if (index == -1) {
            log.debug("err: no folds pai == " + pai);
            return;
        }
        game.gameSeats[game.turn].lestFolds.splice(index, 1);
    }
}

function construct_game_base_info(game) {
    var typeInfo = constant.CreateRoomInfo.type[game.conf.type];
    var baseInfo = {
        type: game.conf.type,
        button: game.button,
        index: game.gameIndex,
        mahjongs: game.mahjongs,
        game_seats: new Array(typeInfo.seatlen)
    };

    for (var i = 0; i < game.gameSeats.length; ++i) {
        baseInfo.game_seats[i] = game.gameSeats[i].holds;
    }
    game.baseInfoJson = JSON.stringify(baseInfo);
}

exports.peng = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.debug("can't find user game data.");
        return;
    }

    //如果是他出的牌，则忽略
    var game = seatData.game;
    if (game.turn == seatData.seatIndex) {
        log.debug("it's your turn.");
        return;
    }

    //如果没有碰的机会，则不能再碰
    if (seatData.canPeng === false) {
        log.debug("seatData.peng == false");
        sendOperations(game, seatData, game.chuPai);
        return;
    }

    //和的了，就不要再来了
    if (seatData.hued) {
        log.debug('you have already hued. no kidding plz.');
        return;
    }

    //如果有别人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        i = (i + 1) % game.gameSeats.length;
        if (i == game.turn) {
            break;
        }
        else {
            var seatInfo = game.gameSeats[i];
            if (seatInfo.canHu && seatInfo.isOper == 0 && i != seatData.seatIndex) {
                log.debug("peng: need wait other hupai, user id is " + seatInfo.userId);
                userMgr.sendMsg(userId, 'wait_other');
                seatData.isOper = 2;
                return;
            } else if (seatInfo.canHu && seatInfo.isOper == 4 && i != seatData.seatIndex) {
                log.debug("peng: other has hu pai ", seatInfo.userId, seatData.userId);
                seatData.isOper = 2;
                return;
            }
        }
    }

    if (seatData.baoTingCount > 0) {  //有过听就不能报听
        seatData.baoTingCount = 0; // 报听只听首轮
    }

    //验证手上的牌的数目
    var pai = game.chuPai;
    var c = seatData.countMap[pai];
    if (c == null || c < 2) {
        log.debug("lack of mj. pai: count: holds: ", pai, c, seatData.holds);
        return;
    }

    //进行碰牌处理,扣掉手上的牌
    for (i = 0; i < 2; ++i) {
        var index = seatData.holds.indexOf(pai);
        if (index == -1) {
            log.debug("peng: can't find mj.", seatData.holds);
            return;
        }
        seatData.holds.splice(index, 1);
        seatData.countMap[pai]--;
    }
    seatData.pengs.push(pai);
    if (game.chuJiPai.isjiPaiType !== -1 && game.chuJiPai.pai === pai) {
        log.debug("set peng ==> jipai", game.chuJiPai.pai);
        _Jipai.insertJiPaiRecord(game, game.chuJiPai.turnSeat, game.chuJiPai.pai, seatData.seatIndex, {
            oper: constant.operType.PENG,
            isChongFengJi: game.chuJiPai.isChongFengJi
        });
        if (game.chuJiPai.isChongFengJi) {
            var setZeRenJiPaiSeat = _Jipai.setZeRenJiPaiSeat(game, seatData.seatIndex, game.chuJiPai.pai);
            if (setZeRenJiPaiSeat !== -1) {
                log.debug("set zerenjipai return seatindex is ", setZeRenJiPaiSeat);
                userMgr.broadcastInRoom('zerenji_push', _Jipai.getZenRenJiBySeatIndex(game, setZeRenJiPaiSeat), seatData.userId, true);
            }
            // userMgr.broadcastInRoom('chongfengji_push', { userId: seatData.userId, type: parseInt(4) }, seatData.userId, true);
        }
        game.chuJiPai.isOper = true;  //标记已经被操作过
    }
    game.chuPai = -1;
    seatData.isOper = 0;
    //清空状态
    clearAllOptions(game);

    //记录数据
    recordGameAction(game, seatData.seatIndex, ACTION_PENG, pai);

    //去掉出牌人的出牌记录
    delChuPaiFromFolds(game, pai);

    //清掉出牌记录
    game.chuPaiData = null;

    if (seatData.baoTingCount > 0) { // 报听
        baoTingCall(game, seatData);
    }

    //广播通知其它玩家
    //userMgr.broadcastInRoom('peng_notify_push',{userid:seatData.userId,pai:pai,turn_userid:game.gameSeats[game.turn].userId},seatData.userId,true);
    userMgr.broadcastInRoom('peng_notify_push', {
        userid: seatData.userId,
        turn_userid: game.gameSeats[game.turn].userId,
        holds: seatData.holds,
        pengs: seatData.pengs,
        folds: game.gameSeats[game.turn].lestFolds
    },
        seatData.userId, true);

    //轮到下家
    moveToNextUser(game, seatData.seatIndex);

    //广播通知玩家出牌方
    seatData.canChuPai = true;
    userMgr.broadcastInRoom('game_chupai_push', seatData.userId, seatData.userId, true);
};

exports.isPlaying = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        return false;
    }

    var game = seatData.game;
    if (game.state == constant.GameStatus.IDLE || game.state === constant.GameStatus.PYAYING) {
        return false;
    }
    return true;
};

//是否能抢杠
function checkCanQiangGang(game, turnSeat, seatData, pai) {
    var hasActions = false;
    for (var i = 0; i < game.gameSeats.length; ++i) {
        //杠牌者不检查
        if (seatData.seatIndex == i) {
            continue;
        }

        //已经和牌的不再检查
        var seatInfo = game.gameSeats[i];
        if (seatInfo.hued) {
            continue;
        }

        //检查是否能胡
        checkCanHu(game, seatInfo, pai, 0);
        if (seatInfo.canHu) {
            hasActions = sendOperations(game, seatInfo, pai) || hasActions;
        }
    }
    if (hasActions) {
        game.qiangGangContext =
            {
                turnSeat: turnSeat,
                seatData: seatData,
                pai: pai,
                isValid: true,
            };
    }
    else {
        game.qiangGangContext = null;
    }
    return game.qiangGangContext != null;
}

function operGang(game, turnSeat, seatData, gangtype, numOfCnt, pai) {
    var seatIndex = seatData.seatIndex;
    var gameTurn = turnSeat.seatIndex;
    var isZhuanShouGang = false;
    if (gangtype === constant.GangPaiType.WANGANG) {
        var idx = seatData.pengs.indexOf(pai);
        if (idx == -1) {
            log.debug("user id == " + seatData.userId + "wan gang delete pai from pengs failed! pai == " + pai);
            return;
        } else {
            seatData.pengs.splice(idx, 1);
        }

        //如果最后一张牌不是杠的牌，则认为是转手杠
        if (seatData.holds[seatData.holds.length - 1] != pai) {
            isZhuanShouGang = true;
        }
    }

    //进行杠牌处理,扣掉手上的牌
    for (var i = 0; i < numOfCnt; ++i) {
        var index = seatData.holds.indexOf(pai);
        if (index == -1) {
            log.debug("gang: can't find mj.", seatData.holds);
            return;
        }
        seatData.holds.splice(index, 1);
        seatData.countMap[pai]--;
    }
    recordGameAction(game, seatData.seatIndex, ACTION_GANG, pai);

    //记录下玩家的杠牌
    var type = constant.operType.ANGANG;
    if (gangtype === constant.GangPaiType.ANGANG) //暗杠
    {
        seatData.angangs.push(pai);
        recordUserAction(game, seatData, constant.GangPaiType.ANGANG);

        seatData.angangScore[pai] = {
            pai: pai,
            score: 3 * (game.gameSeats.length - 1),
            seatId: seatIndex,
        };
        for (let i = 0; i < game.gameSeats.length; ++i) {
            let seat = game.gameSeats[i];
            if (seat != null && seat.seatIndex != seatIndex) {
                seat.angangScore[pai] = {
                    pai: pai,
                    score: -3,
                    seatId: seatIndex,
                };
            }
        }
        for (let i = 0; i < game.gameSeats.length; ++i) {
            var seat = game.gameSeats[i];
            log.debug("1 seat gang set ", seat.userId, seat.diangangScore, seat.angangScore, seat.wangangScore);
        }
    }
    else if (gangtype === constant.GangPaiType.MINGGANG) //明杠
    {
        type = constant.operType.MINGANG;
        seatData.diangangs.push(pai);
        recordUserAction(game, seatData, constant.GangPaiType.MINGGANG, gameTurn);
        var fs = turnSeat;
        recordUserAction(game, fs, "fanggang", seatIndex);

        var index_gang = fs.lestFolds.indexOf(pai);
        if (fs.lestFolds != null && index_gang != -1) {
            fs.lestFolds.splice(index_gang, 1);
        }

        seatData.diangangScore[pai] = {
            pai: pai,
            score: 2,
            seatId: seatIndex,
        };
        turnSeat.diangangScore[pai] = {
            pai: pai,
            score: -2,
            seatId: seatIndex,
        };
    }
    else if (gangtype == constant.GangPaiType.WANGANG) //弯杠
    {
        type = constant.operType.WANGANG;
        seatData.wangangs.push(pai);
        var ac = recordUserAction(game, seatData, constant.GangPaiType.WANGANG);
        ac.score = game.conf.baseScore;

        if (game.noScoreGang == null || -1 == game.noScoreGang.indexOf(pai)) {
            seatData.wangangScore[pai] = {
                pai: pai,
                score: 4 * (game.gameSeats.length - 1),
                seatId: seatIndex,
            };
            for (let i = 0; i < game.gameSeats.length; ++i) {
                let seat = game.gameSeats[i];
                if (seat != null && seat.seatIndex != seatIndex) {
                    seat.wangangScore[pai] = {
                        pai: pai,
                        score: -4,
                        seatId: seatIndex,
                    };
                }
            }
        } else {
            gangtype = constant.GangPaiType.NOSCOREGANG;
            type = constant.operType.NOSCOREGANG;
        }
    }

    //记录所有成功杠牌
    seatData.gangs.push({
        pai: pai,
        type: gangtype
    });

    //杠牌
    if (game.chuJiPai.isjiPaiType !== -1 && game.chuJiPai.pai === pai) {
        log.debug("set gang ==> jipai", type, game.chuJiPai.pai);
        _Jipai.insertJiPaiRecord(game, game.chuJiPai.turnSeat, game.chuJiPai.pai, seatData.seatIndex, {
            oper: type,
            isChongFengJi: game.chuJiPai.isChongFengJi
        });
        if (gangtype === constant.GangPaiType.MINGGANG && game.chuJiPai.isChongFengJi) {
            var setZeRenJiPaiSeat = _Jipai.setZeRenJiPaiSeat(game, game.chuJiPai.turnSeat, game.chuJiPai.pai);
            if (setZeRenJiPaiSeat !== -1) {
                log.debug("set zerenjipai return seatindex is ", setZeRenJiPaiSeat);
                userMgr.broadcastInRoom('zerenji_push', _Jipai.getZenRenJiBySeatIndex(game, setZeRenJiPaiSeat), seatData.userId, true);
            }
        }
        game.chuJiPai.isOper = true;  //标记已经被操作过
    } else { //    暗杠自摸
        if (game.chuJiPai.isjiPaiType !== -1) {
            game.chuJiPai.isOper = true;  //标记已经被操作过
        }
        if (gangtype === constant.GangPaiType.ANGANG) {
            log.debug("set gang ==> jipai", type, pai);
            var jiPaiType = constant.getJiPaiTypeByPai(pai);
            if (jiPaiType !== -1) {
                _Jipai.insertJiPaiRecord(game, seatIndex, pai, seatIndex, {
                    oper: type,
                    isChongFengJi: false
                });
            }
        }
        if (gangtype === constant.GangPaiType.NOSCOREGANG) {
            log.debug("set WANGANG gang ==> jipai", type, pai);
            var jiPaiType = constant.getJiPaiTypeByPai(pai);
            if (jiPaiType !== -1) {
                _Jipai.insertJiPaiRecord(game, seatIndex, pai, seatIndex, {
                    oper: type,
                    isChongFengJi: false
                });
            }
        }
    }

    //清除状态
    clearAllOptions(game);

    //检测听牌
    checkCanTingPai(game, seatData);

    //去掉出牌人的出牌记录
    if (gangtype == constant.GangPaiType.MINGGAN) {
        delChuPaiFromFolds(game, pai);
    }
    game.chuPaiData = null;

    //通知其他玩家，有人杠了牌
    seatData.isOper = 0;
    seatData.OperPai = -1;
    userMgr.broadcastInRoom('gang_notify_push', {
        userid: seatData.userId,
        gangtype: gangtype,
        turn_userid: game.gameSeats[game.turn].userId,
        holds: seatData.holds,
        angangs: seatData.angangs,
        diangangs: seatData.diangangs,
        wangangs: seatData.wangangs,
        pengs: seatData.pengs,
        folds: game.gameSeats[game.turn].lestFolds
    },
        seatData.userId, true);

    //判断是否结束,黄庄
    if (game.currentIndex == game.mahjongs.length) {
        for (var index in game.gameOverCallback) {
            game.gameOverCallback[index](game, seatData, 1);
        }
        return;
    }

    //变成自己的轮子
    moveToNextUser(game, seatIndex);

    //再次摸牌
    nextUserMoPai(game);

    //只能放在这里。因为过手就会清除杠牌标记
    seatData.lastFangGangSeat = gameTurn;
}

//杠操作
exports.gang = function (userId, pai) {
    log.debug("follow is gangpai data: userId == " + userId + " pai == " + pai);
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.debug("can't find user game data.");
        return;
    }
    // var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //如果没有杠的机会，则不能再杠
    if (seatData.canGang === false) {
        log.debug("seatData.gang == false");
        return;
    }

    //和的了，就不要再来了
    if (seatData.hued) {
        log.debug('you have already hued. no kidding plz.');
        return;
    }

    //没数据不能杠
    if (seatData.gangPai.indexOf(pai) == -1) {
        log.debug("the given pai can't be ganged.");
        return;
    }

    //如果有人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        i = (i + 1) % game.gameSeats.length;
        if (i == game.turn) {
            break;
        }
        else {
            var seatInfo = game.gameSeats[i];
            if (seatInfo.canHu && seatInfo.isOper == 0 && i != seatData.seatIndex) {
                log.debug("need wait,you can not gang until other is hued.");
                userMgr.sendMsg(userId, 'wait_other');
                seatData.isOper = 3;
                seatData.OperPai = pai;
                return;
            } else if (seatInfo.canHu && seatInfo.isOper == 4 && i != seatData.seatIndex) {
                log.debug("peng: other has hu pai ", seatInfo.userId, seatData.userId);
                return;
            }
        }
    }
    if (seatData.baoTingCount > 0) {  //有过扛就不能报听
        seatData.baoTingCount = 0; // 报听只听首轮
    }

    //判断杠的类型
    var numOfCnt = seatData.countMap[pai];
    var gangtype = -1;
    if (numOfCnt == 1)   //弯杠,因为去掉了碰牌
    {
        gangtype = constant.GangPaiType.WANGANG;
    }
    else if (numOfCnt == 3) {
        gangtype = constant.GangPaiType.MINGGANG;
    }
    else if (numOfCnt == 4) {
        gangtype = constant.GangPaiType.ANGANG;
    }
    else {
        log.debug("invalid pai count.");
        return;
    }

    //杠牌后清状态并广播
    seatData.canChuPai = false;

    //如果是弯杠，则需要检查是否可以抢杠
    var turnSeat = game.gameSeats[game.turn];
    if (numOfCnt == 1) {
        if (seatData.isOper != 3) {
            var canQiangGang = checkCanQiangGang(game, turnSeat, seatData, pai);
            if (canQiangGang) {
                log.debug("need wait,other need qianggang.");
                userMgr.sendMsg(userId, 'wait_other');
                seatData.isOper = 3;
                seatData.OperPai = pai;

                // for (var i = 0; i < game.gameSeats.length; ++i) {
                //     var seat = game.gameSeats[i];
                //     if (seat.seatIndex != seatData.seatIndex) {
                //         seat.score -= 4;
                //         log.debug("qianggang other: user id == " + seat.userId + " seat score == " + seat.score);
                //     }
                // }
                // seatData.wangangScore[pai] += ((game.gameSeats.length - 1) * 4);
                // seatData.score += ((game.gameSeats.length - 1) * 4);
                // log.debug("beiqianggang: user id == " + seatData.userId + " seatData score == " + seatData.wangangScore[pai]);
                return;
            }
        }
    }

    //杠牌
    game.chuPai = -1;
    operGang(game, turnSeat, seatData, gangtype, numOfCnt, pai);
};

exports.hu = function (userId) {
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.debug("can't find user game data.");
        var content = "can't find user game data.";
        userMgr.sendMsg(userId, 'error_msg', { userId: userId, content: content });
        return;
    }

    var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //校验是否能胡
    // var bTianHu = false;
    if (seatData.canHu === false) {
        log.debug("err: you can not hu pai, status is wrong!");
        content = "err: you can not hu pai!";
        userMgr.sendMsg(userId, 'error_msg', { userId: userId, content: content });
        return;
    }

    if (seatData.canHu && game.chuPai == INVALID_PAI && game.chupaiCnt === 0) {
        //是否听牌是天胡
        if (!(seatData.pattern == "tianhu")) {
            log.debug("you not chu pai and are not tianhu, invalid hu pai.");
            content = "err: you can not hu pai,you are not tianhu";
            userMgr.sendMsg(userId, 'error_msg', { userId: userId, content: content });
            return;
        }
    }

    //和的了，就不要再来了
    if (seatData.hued) {
        log.debug('you have already hued. no kidding plz.');
        return;
    }

    var hupai = game.chuPai;
    var isZimo = false;
    var turnSeat = game.gameSeats[game.turn];
    seatData.isGangHu = turnSeat.lastFangGangSeat >= 0;
    var notify = -1;
    if (game.qiangGangContext != null) {
        var gangSeat = game.qiangGangContext.seatData;
        hupai = game.qiangGangContext.pai;
        notify = hupai;
        recordUserAction(game, seatData, constant.TingPaiType.QIANGGANGHU, gangSeat.seatIndex); //抢杠胡
        // recordGameAction(game, seatIndex, ACTION_HU, hupai);
        seatData.isQiangGangHu = true;
        game.qiangGangContext.isValid = false;

        log.debug("qiang gang hu pai == ", seatData.userId, hupai);
        var idx = gangSeat.holds.indexOf(hupai);
        if (idx != -1) {
            gangSeat.holds.splice(idx, 1);
            gangSeat.countMap[hupai]--;
            log.debug("gang seat , gang seat holds == hupai == ", gangSeat.userId, gangSeat.holds, hupai);
            userMgr.sendMsg(gangSeat.userId, 'game_holds_push', gangSeat.holds);
        }

        seatData.copyCountMap = _.cloneDeep(seatData.countMap); //copy一份鸡牌要用到
        if (seatData.copyCountMap[hupai] > 0) {  // 鸡牌有胡牌记录，需清除
            seatData.copyCountMap[hupai] = 0;
        }
        //将牌添加到玩家的手牌列表，供前端显示
        seatData.holds.push(hupai);
        if (seatData.countMap[hupai]) {
            seatData.countMap[hupai]++;
        } else {
            seatData.countMap[hupai] = 1;
        }

        recordUserAction(game, gangSeat, "beiqianggang", seatIndex);
    }
    else if (game.chuPai == -1) { //自摸
        hupai = seatData.holds[seatData.holds.length - 1];
        notify = -1;
        if (seatData.isGangHu) {
            recordUserAction(game, seatData, constant.TingPaiType.GANGSHANGHUA);  //杠上花
        }

        isZimo = true;

        seatData.copyCountMap = _.cloneDeep(seatData.countMap); //copy一份鸡牌要用到,不包含胡的那张鸡牌
        if (seatData.copyCountMap[hupai] > 0) {  // 鸡牌有胡牌记录，需清除
            seatData.copyCountMap[hupai] = 0;
        }
        recordGameAction(game, seatIndex, ACTION_ZIMO, hupai);
    }
    else {   // 放炮的情况
        notify = game.chuPai;

        seatData.copyCountMap = _.cloneDeep(seatData.countMap); //copy一份鸡牌要用到,不包含胡的那张鸡牌
        if (seatData.copyCountMap[notify] > 0) {  // 鸡牌有胡牌记录，需清除
            seatData.copyCountMap[notify] = 0;
        }
        //将牌添加到玩家的手牌列表，供前端显示
        if (seatData.holds.length % 3 == 1) {
            seatData.holds.push(game.chuPai);
            if (seatData.countMap[game.chuPai]) {
                seatData.countMap[game.chuPai]++;
            }
            else {
                seatData.countMap[game.chuPai] = 1;
            }
        }
        log.debug("hu user id holds = ", seatData.userId, seatData.holds);

        // var hu_type = "hu";
        if (turnSeat.lastFangGangSeat >= 0) {
            recordUserAction(game, seatData, constant.TingPaiType.GANGSHANGPAO, game.turn); //杠上炮
            if (turnSeat.gangs != null && turnSeat.gangs.length > 0) {
                game.noScoreGang.push(turnSeat.gangs[turnSeat.gangs.length - 1].pai);  //杠上炮的杠不得分
            }
        }
        //记录玩家放炮信息
        var fs = game.gameSeats[game.turn];
        recordUserAction(game, fs, "fangpao", seatIndex);
        recordGameAction(game, seatIndex, ACTION_HU, hupai);
        game.fangpaoshumu++;
    }

    if (game.hupaiList.indexOf(userId) == -1) {
        game.hupaiList.push(userId);
    }

    //保存
    var pai = game.chuPai;

    seatData.iszimo = isZimo;

    //通知前端，有人和牌了
    userMgr.broadcastInRoom('hu_push', { seatindex: seatIndex, iszimo: isZimo, hupai: notify }, seatData.userId, true);

    //已经点击胡牌了,碰杠无效
    if (seatData.canPeng) {
        seatData.canPeng = false;
    }
    if (seatData.canGang) {
        seatData.canGang = false;
    }

    var saveHuPaiJfenScore = function () {
        if (game.chuPai == -1) {
            pai = seatData.holds[seatData.holds.length - 1];
        }
        if (game.chuJiPai.isjiPaiType !== -1) {
            var type;
            if (seatData.countMap[pai] == 1) {
                type = constant.operType.HUO;
            } else if (seatData.countMap[pai] == 2) {
                type = constant.operType.HUW;
            } else if (seatData.countMap[pai] == 3) {
                type = constant.operType.HUT;
            } else if (seatData.countMap[pai] == 4) {
                type = constant.operType.HUF;
            }
            log.debug("set hupai ========> jipai", game.chuJiPai.pai, type, game.chuJiPai.pai, seatData.countMap[pai]);
            _Jipai.insertJiPaiRecord(game, game.chuJiPai.turnSeat, game.chuJiPai.pai, seatData.seatIndex, {
                oper: type,
                isChongFengJi: game.chuJiPai.isChongFengJi
            });
            // 别人放炮才记录责任鸡
            if (game.chuPai !== -1 && game.chuJiPai.isChongFengJi) {
                var setZeRenJiPaiSeat = _Jipai.setZeRenJiPaiSeat(game, seatData.seatIndex, game.chuJiPai.pai);
                if (setZeRenJiPaiSeat !== -1) {
                    log.debug("set zerenjipai return seatindex is ", setZeRenJiPaiSeat);
                    userMgr.broadcastInRoom('zerenji_push', _Jipai.getZenRenJiBySeatIndex(game, setZeRenJiPaiSeat), seatData.userId, true);
                }
            }
            game.chuJiPai.isOper = true;  //标记已经被操作过
        }
    }

    //如果有别人可以胡牌，则需要等待
    var i = game.turn;
    while (true) {
        i = (i + 1) % game.gameSeats.length;
        if (i == game.turn) {
            break;
        }
        else {
            var seatInfo = game.gameSeats[i];
            //清掉别人的碰杠
            if (seatInfo.canPeng || seatInfo.canGang) {
                seatInfo.canPeng = false;
                seatInfo.canGang = false;
                sendOperations(game, seatInfo, game.chuPai);
            }
            if (seatInfo.canHu && seatInfo.isOper == 0 && i != seatData.seatIndex) {
                log.debug("peng: need wait other hupai, user id is " + seatInfo.userId);
                userMgr.sendMsg(userId, 'wait_other');
                seatData.isOper = 4;
                saveHuPaiJfenScore();  // 存储鸡分
                return;
            }
        }
    }


    //胡牌了
    saveHuPaiJfenScore(); // 存储鸡分
    seatData.hued = true;
    clearAllOptions(game, seatData);
    if (seatData.isOper <= 0) {
        seatData.isOper = 4;
    }

    if (game.lastHuPaiSeat == -1) {
        game.lastHuPaiSeat = seatIndex;
    } else {
        var lp = (game.lastFangGangSeat - game.turn + game.gameSeats.length) % game.gameSeats.length;
        var cur = (seatData.seatIndex - game.turn + game.gameSeats.length) % game.gameSeats.length;
        if (cur > lp) {
            game.lastHuPaiSeat = seatData.seatIndex;
        }
    }

    var huNumber = 0; //还没点胡牌的数量
    for (var index in game.gameSeats) {
        if (game.gameSeats[index].canHu && game.gameSeats[index].isOper == 0) {
            huNumber++;
            log.debug("not press hu button user id ", game.gameSeats[index].userId);
        }
    }

    // 结算. 所有人都胡牌了
    if (huNumber == 0) {
        //清空状态
        clearAllOptions(game);

        //清空操作
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var ddd = game.gameSeats[i];
            ddd.canChuPai = false;
            ddd.isOper = 0;
            sendOperations(game, ddd, hupai);
        }

        //胡牌了事
        for (var index in game.gameOverCallback) {
            game.gameOverCallback[index](game, seatData, 0);
        }
    }
    else {
        userMgr.sendMsg(userId, 'wait_other');
    }
};

function checkActionValid(game, seatData, seatIndex) {
    //找出剩余已操作的带胡牌玩家数量
    var operHuIndex = [];    //已经操作带胡牌动作的玩家座位号
    var noOperHuIndex = [];  //没操作胡牌的玩家座位号
    var operPGIndex = [];    //已经操作带碰杠牌动作的玩家座位号
    var noOperPGIndex = [];   //没操作带碰杠牌动作的玩家座位号

    for (var i = 0; i < game.gameSeats.length; ++i) {
        var seatInfo = game.gameSeats[i];
        if (seatInfo != null && seatInfo.seatIndex != seatIndex) {
            if (hasOperations(seatInfo) || seatInfo.isOper > 0) {
                if (seatInfo.isOper == 4   //已操作胡
                    || (seatInfo.canHu && seatInfo.isOper > 0 && seatInfo.isOper != 4)) {  //除了胡，还有碰杠的玩家，已操作碰或杠
                    operHuIndex.push(seatInfo.seatIndex);
                    log.debug("has hu or press peng or gang user ", seatInfo.userId);
                } else if (seatInfo.canHu && seatInfo.isOper == 0) { //没操作胡
                    noOperHuIndex.push(seatInfo.seatIndex);
                    log.debug("no press hu user ", seatInfo.userId);
                } else if (!seatInfo.canHu && seatInfo.isOper > 0 && seatInfo.isOper < 4) {
                    operPGIndex.push(seatInfo.seatIndex);
                    log.debug("has press peng or gang user ", seatInfo.userId);
                } else if (!seatInfo.canHu && (seatInfo.canPeng || seatInfo.canGang) && seatInfo.isOper == 0) {
                    noOperPGIndex.push(seatInfo.seatIndex);
                    log.debug("no press peng or gang user ", seatInfo.userId);
                }
            }
        }
    }

    //有胡操作,如果有人没点,则等待
    if (noOperHuIndex.length > 0) {
        log.debug("user guo wait other hu", seatData.userId, game.gameSeats[noOperHuIndex[0]].userId);
        userMgr.sendMsg(seatData.userId, 'wait_other');
        return true;
    }

    //都操作了胡牌
    if (operHuIndex.length > 0) {
        for (let i = 0; i < operHuIndex.length; ++i) {
            var index = operHuIndex[i];
            let seatInfo = game.gameSeats[index];
            if (seatInfo != null && seatInfo.isOper == 4) {  //继续胡牌
                exports.hu(seatInfo.userId, game.chuPai);
                log.debug("hu seat id ", seatInfo.userId, game.chuPai);
                return true;
            }
        }
        for (let i = 0; i < operHuIndex.length; ++i) {
            var index = operHuIndex[i];
            let seatInfo = game.gameSeats[index];
            if (seatInfo.isOper == 3) {
                exports.gang(seatInfo.userId, seatInfo.OperPai);  //继续杠牌
                log.debug("1 gang seat id ", seatInfo.userId, game.chuPai);
                return true;
            } else if (seatInfo.isOper == 2) {
                exports.peng(seatInfo.userId, game.chuPai);     //继续碰牌
                log.debug("1 peng seat id ", seatInfo.userId, game.chuPai);
                return true;
            }
        }
    }

    //有碰或杠操作,如果有人没点,则等待
    if (noOperPGIndex.length > 0) {
        log.debug("user guo wait other peng or gang ", seatData.userId, game.gameSeats[noOperPGIndex[0]].userId);
        userMgr.sendMsg(seatData.userId, 'wait_other');
        return true;
    }

    //如果有人已经碰杠，则执行
    if (noOperPGIndex.length == 0) {
        for (var i = 0; i < game.gameSeats.length; ++i) {
            var seatInfo = game.gameSeats[i];
            if (seatInfo != null && seatInfo.seatIndex != seatIndex) {
                if (seatInfo.canGang && seatInfo.isOper == 3) {
                    exports.gang(seatInfo.userId, seatInfo.OperPai);  //继续杠牌
                    log.debug("2 gang seat id ", seatInfo.userId, game.chuPai);
                    return true;
                }
                else if (seatInfo.canPeng && seatInfo.isOper == 2) {
                    exports.peng(seatInfo.userId, game.chuPai);  //继续碰牌
                    log.debug("2 peng seat id ", seatInfo.userId, game.chuPai);
                    return true;
                }
            }
        }
    }
    return false;
}
//弃牌
exports.guo = function (userId) {
    log.debug("user id send guo message ", userId);
    var seatData = gameSeatsOfUsers[userId];
    if (seatData == null) {
        log.debug("guo: can't find user game data.");
        return;
    }

    var seatIndex = seatData.seatIndex;
    var game = seatData.game;

    //如果玩家没有对应的操作，则也认为是非法消息
    if ((seatData.canGang || seatData.canPeng || seatData.canHu) == false) {
        log.debug("guo: no need guo.");
        sendOperations(game, seatData, game.chuPai);
        return;
    }
    userMgr.sendMsg(seatData.userId, "guo_result");
    if (seatData.canPeng) {
        if (seatData.qiPengHu != null && game.chuPai != -1) {
            seatData.qiPengHu.push({
                chuPaiPlayer: game.gameSeats[game.turn].userId,
                operPlayer: seatData.userId,
                operType: 1,
                pai: game.chuPai,
            });
            log.debug("user id == , after push, qi peng setting == ", seatData.userId, seatData.qiPengHu);
        }
    }
    if (seatData.canHu) {
        if (game.chuPai != -1) {
            seatData.qiPengHu.push({
                chuPaiPlayer: game.gameSeats[game.turn].userId,
                operPlayer: seatData.userId,
                operType: 2,
                pai: game.chuPai,
            });
        }
        else {
            seatData.qiPengHu.push({
                chuPaiPlayer: seatData.userId,
                operPlayer: seatData.userId,
                operType: 2,
                pai: seatData.holds[seatData.holds.length - 1],
            });
        }
        log.debug("user id == , after push, qi peng setting == ", seatData.userId, seatData.qiPengHu);
    }


    //如果是玩家自己的轮子，不是接牌，则不需要额外操作, 或者明显已经摸牌了,那就要打出才对 
    var doNothing = game.chuPai == -1 && game.turn == seatIndex;
    if (doNothing || seatData.holds.length % 3 == 2) {
        log.debug("guo: self return, qi peng gang hu?");
        //没有出过牌  //自摸
        if (seatData.canHu && seatData.baoTingCount > 0) {
            baoTingCall(game, seatData);
        }
        clearAllOptions(game, seatData, 1);
        return;
    }

    clearAllOptions(game, seatData);

    if (game.gameSeats.length > 2) {
        if (checkActionValid(game, seatData, seatIndex)) {
            log.debug("need wait or action finish! ", seatData.userId);
            return;
        }
    }

    //如果是已打出的牌，则需要通知。
    if (game.chuPai >= 0) {
        game.chuPai = -1;
    }

    var qiangGangContext = game.qiangGangContext;

    //清除所有的操作
    clearAllOptions(game);

    //没牌了就结算,黄庄
    if (game.currentIndex === game.mahjongs.length) {
        for (var index in game.gameOverCallback) {
            game.gameOverCallback[index](game, seatData, 1);
        }
        return;
    }

    if (qiangGangContext != null && qiangGangContext.isValid) {
        operGang(game, qiangGangContext.turnSeat, qiangGangContext.seatData, "wangang", 1, qiangGangContext.pai);
    }
    else {
        //下家摸牌
        moveToNextUser(game);
        nextUserMoPai(game);
    }
};

exports.hasBegan = function (roomId) {
    var game = games[roomId];
    if (game != null) {
        return true;
    }
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo != null) {
        return roomInfo.numOfGames > 0;
    }
    return false;
};

exports.doDissolve = function (roomId) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    var game = games[roomId];
    doGameOver(game, roomInfo.seats[0].userId, 2);
};

exports.dissolveRequest = function (roomId, userId) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    if (roomInfo.dr != null) {
        log.debug("dissolveRequest room info dr ", roomInfo.dr, dissolvingList);
        return roomInfo;
    }

    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }

    var typeInfo = constant.CreateRoomInfo.type[roomInfo.conf.type];
    roomInfo.dr = {
        endTime: Date.now() + 180000,
        states: new Array(typeInfo.seatlen),
    };
    for (var i = 0; i < typeInfo.seatlen; ++i) {
        if (i != seatIndex) {
            roomInfo.dr.states[i] = false;
        } else {
            roomInfo.dr.states[seatIndex] = true;
        }
    }
    if (dissolvingList.indexOf(roomId) == -1) {
        dissolvingList.push(roomId);
    }
    log.debug("dissolveRequest ", dissolvingList, roomId, userId, roomInfo.dr, typeInfo.seatlen);

    return roomInfo;
};

exports.dissolveAgree = function (roomId, userId, agree) {
    var roomInfo = roomMgr.getRoom(roomId);
    if (roomInfo == null) {
        return null;
    }

    if (roomInfo.dr == null) {
        log.debug("dissolveAgree room info dr 1 ", roomInfo.dr, dissolvingList);
        return roomInfo;
    }

    var seatIndex = roomMgr.getUserSeat(userId);
    if (seatIndex == null) {
        return null;
    }

    if (agree) {
        roomInfo.dr.states[seatIndex] = true;
        log.debug("dissolveAgree room info dr 2 ", roomInfo.dr, dissolvingList);
    }
    else {
        roomInfo.dr = null;
        var idx = dissolvingList.indexOf(roomId);
        if (idx != -1) {
            dissolvingList.splice(idx, 1);
            log.debug("cancel dissolveAgree ", dissolvingList, roomId, userId);
        }
    }
    return roomInfo;
};

function update() {
    for (var i = dissolvingList.length - 1; i >= 0; --i) {
        var roomId = dissolvingList[i];
        var roomInfo = roomMgr.getRoom(roomId);
        if (roomInfo != null && roomInfo.dr != null && roomInfo.dr.endTime != null) {
            if (roomInfo.dr.endTime > 0 && roomInfo.dr.endTime < Date.now()) {
                dissolvingList.splice(i, 1);
                log.debug("request to delete room success ", dissolvingList, roomId);
                exports.doDissolve(roomId);
            }
        } else {
            log.debug("request to delete room failed ", dissolvingList, roomId);
            dissolvingList.splice(i, 1);
        }
    }

    for (var key in roomRes) {
        if (roomRes[key] != null && (roomRes[key].isSend || Date.now() - roomRes[key].saveTime > 30000)) {
            console.log("will delete room info key roomRes ", key, roomRes[key]);
            delete roomRes[key];
            break;
        }
    }
}

setInterval(update, 1000);

function shuffleTest(game) {
    //17+3*16 = 65张牌
    var buttonPai = [];//[0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,6];
    var player1 = [];//[9,9,9,10,10,10,11,11,11,12,12,12,13,13,13,14];
    var player2 = [];//[18,18,18,19,19,19,20,20,20,21,21,21,22,22,22,23];
    var player3 = [];//[27,27,27,28,28,28,29,29,29,30,30,30,31,31,31,33];
    var caishenpai = 0;

    //是否测试
    var bFlag = configPai.GetHoldsPai(buttonPai, player1, player2, player3, function (data) {
        if (data != null) {
            game.caishenPai = data;
        }
    });
    if (!bFlag) {
        //洗牌
        shuffle(game);

        //发牌
        deal(game);

        return;
    }

    var mahjongs = game.mahjongs;
    //筒 0 ~ 8 表示筒子,条 9 ~ 17表示条子,万 18 ~ 26表示万
    var index = 0;
    var mahjongsTmp = [];
    for (var i = 0; i <= maxPai; ++i) {
        for (var c = 0; c < 4; ++c) {
            mahjongsTmp[index] = i;
            game.mahjongs[index] = i;
            index++;
        }
    }

    index = 0;
    for (var i = 0; i < 13; ++i) {
        game.mahjongs[index] = buttonPai[i];
        ++index;
        game.mahjongs[index] = player1[i];
        ++index;
        game.mahjongs[index] = player2[i];
        ++index;
        game.mahjongs[index] = player3[i];
        ++index;
    }
    game.mahjongs[index] = buttonPai[13];
    ++index;

    //手牌
    for (var i = 0; i < 13; ++i) {
        if (game.gameSeats[game.button].holds == null) {
            game.gameSeats[game.button].holds = [];
        }
        game.gameSeats[game.button].holds.push(buttonPai[i]);
        if (game.gameSeats[game.button].countMap[buttonPai[i]] == null) {
            game.gameSeats[game.button].countMap[buttonPai[i]] = 1;
        }
        else {
            game.gameSeats[game.button].countMap[buttonPai[i]] += 1;
        }
        if (game.gameSeats[(game.button + 1) % game.gameSeats.length].holds == null) {
            game.gameSeats[(game.button + 1) % game.gameSeats.length].holds = [];
        }
        game.gameSeats[(game.button + 1) % game.gameSeats.length].holds.push(player1[i]);
        if (game.gameSeats[(game.button + 1) % game.gameSeats.length].countMap[player1[i]] == null) {
            game.gameSeats[(game.button + 1) % game.gameSeats.length].countMap[player1[i]] = 1;
        }
        else {
            game.gameSeats[(game.button + 1) % game.gameSeats.length].countMap[player1[i]] += 1;
        }
        game.gameSeats[(game.button + 2) % game.gameSeats.length].holds.push(player2[i]);
        if (game.gameSeats[(game.button + 2) % game.gameSeats.length].countMap[player2[i]] == null) {
            game.gameSeats[(game.button + 2) % game.gameSeats.length].countMap[player2[i]] = 1;
        }
        else {
            game.gameSeats[(game.button + 2) % game.gameSeats.length].countMap[player2[i]] += 1;
        }
        game.gameSeats[(game.button + 3) % game.gameSeats.length].holds.push(player3[i]);
        if (game.gameSeats[(game.button + 3) % game.gameSeats.length].countMap[player3[i]] == null) {
            game.gameSeats[(game.button + 3) % game.gameSeats.length].countMap[player3[i]] = 1;
        }
        else {
            game.gameSeats[(game.button + 3) % game.gameSeats.length].countMap[player3[i]] += 1;
        }
    }

    //检测是否听牌
    // for (var i = 0; i < game.gameSeats.length; ++i) {
    //     checkCanTingPai(game, game.gameSeats[i]);
    // }

    game.gameSeats[game.button].holds.push(buttonPai[13]);
    if (game.gameSeats[game.button].countMap[buttonPai[13]] == null) {
        game.gameSeats[game.button].countMap[buttonPai[13]] = 1;
    }
    else {
        game.gameSeats[game.button].countMap[buttonPai[13]] += 1;
    }

    // // //检查是否可以暗杠或者胡
    // checkCanAnGang(game, game.gameSeats[game.button]);

    // // //检查胡，直杠，弯杠
    // checkCanWanGang(game, game.gameSeats[game.button], buttonPai[13]);

    // // //检查看是否可以和
    // checkCanHu(game, game.gameSeats[game.button], buttonPai[13], 1);

    //从麻将堆里减掉发牌的牌
    for (var j = mahjongsTmp.length - 1; j >= 0; --j) {
        for (var k = buttonPai.length - 1; k >= 0; --k) {
            if (mahjongsTmp[j] == buttonPai[k]) {
                mahjongsTmp.splice(j, 1);
                buttonPai.splice(k, 1);
                break;
            }
        }
    }
    for (var j = mahjongsTmp.length - 1; j >= 0; --j) {
        for (var k = player1.length - 1; k >= 0; --k) {
            if (mahjongsTmp[j] == player1[k]) {
                mahjongsTmp.splice(j, 1);
                player1.splice(k, 1);
                break;
            }
        }
    }

    for (var j = mahjongsTmp.length - 1; j >= 0; --j) {
        for (var k = player2.length - 1; k >= 0; --k) {
            if (mahjongsTmp[j] == player2[k]) {
                mahjongsTmp.splice(j, 1);
                player2.splice(k, 1);
                break;
            }
        }
    }
    for (var j = mahjongsTmp.length - 1; j >= 0; --j) {
        for (var k = player3.length - 1; k >= 0; --k) {
            if (mahjongsTmp[j] == player3[k]) {
                mahjongsTmp.splice(j, 1);
                player3.splice(k, 1);
                break;
            }
        }
    }

    //洗剩余牌
    for (var i = 0; i < mahjongsTmp.length; ++i) {
        var lastIndex = mahjongsTmp.length - 1 - i;
        var index2 = Math.floor(Math.random() * lastIndex);
        var t = mahjongsTmp[index2];
        mahjongsTmp[index2] = mahjongsTmp[lastIndex];
        mahjongsTmp[lastIndex] = t;
    }

    for (var i = 0; i < mahjongsTmp.length; ++i) {
        game.mahjongs[index] = mahjongsTmp[i];
        ++index;
    }
    game.mahjongs[53] = 12;
    // game.mahjongs[40] = 3;
    // game.mahjongs[41] = 3;
    // game.mahjongs[42] = 3;
    //game.mahjongs[53] = 11;
    //game.mahjongs[53] = 11;
    // game.mahjongs[57] = 5;
    // game.mahjongs[61] = 4;

    game.turn = game.button;
    game.currentIndex = 27;
}

function TestModel(game) {
    shuffleTest(game);
}